Tools & Tips/Pandas

판다스(pandas) csv/xlsx 파일 불러오기 list/dict가 문자열로 변환되어 있는 경우 해결 방법

IP_DataScientist 2022. 9. 12.
반응형

판다스 데이터프레임 컬럼에 리스트형태를 입력하고 저장한 후

다시 읽어오면, 해당 형식은 문자열이다 -> "[1, 2, 3, 4]"

이걸 다시 리스트화 시키는 방법에 대해서 작성하겠다.

 

우선 리스트가 들어있는 데이터 프레임을 생성해 보겠다.

여기서 열은 '회원 컬럼' 이고 총 3개의 다른 리스트가 들어있는 행이 생성된다.

import pandas as pd
from ast import literal_eval

df = pd.DataFrame({'회원 컬럼':[['홍길동', '10', '남'], ['이정재', '20', '남'], ['하정우', '30', '남']]})
df.info()

Dtype 은 object 로 나온다.

행 안에 있는 데이터의 타입을 보면 list 형태인 것을 확인할 수 있다.

df['회원 컬럼'][0]
>> ['홍길동', '10', '남']
type(df['회원 컬럼'][0])
>> list

이제 여기까지 데이터를 만들었으니 다음에 이어서 해야지~ 하고 저장을 한 후에 df2로 불러오기를 해보자.

df.to_csv('연예인.csv', index=False, sep = '\t')
df2 = pd.read_csv('./연예인.csv', sep='\t')

이렇게만 보았을때 회원 컬럼의 각 행은 리스트로 보이는게 함정이다...

역시 0번 행의 형태를 보면 str로 보인다.

df2['회원 컬럼'][0]
>> "['홍길동', '10', '남']"
type(df2['회원 컬럼'][0])
>> str

ast 의 literal_eval 은 이 문제를 아주 간단하게 해결해주는 도구이다.

다음은 pandas의 apply와 lambda를 활용해서 전체 열 값에 한번에 literal_eval을 적용하는 코드이다.

항상 데이터프레임을 읽어올때, 리스트로 보이는 값들이 있으면, 꼭 한번 확인해보는 습관을 들이는게 중요해 보인다.

df2['회원 컬럼'] = df2['회원 컬럼'].apply(lambda x : literal_eval(x))

df2['회원 컬럼'][0]
>> ['홍길동', '10', '남']
type(df2['회원 컬럼'][0])
>> list

그럼 이제 ast가 도대체 뭔지? 에 대해서 살펴보자..

ast — Abstract Syntax Trees

vscode에서 ctrl 을 누른뒤 import 하는 ast 를 클릭하여 조금 더 깊이 들어가면 docstring으로 된 친절한 설명? 을 만나볼 수 있다.

ast

google  번역 성능이 날이갈 수록 좋아지는걸 느낀다..(번역할 필요를 못느낄정도로..)

 

'ast' 모듈은 Python 애플리케이션이 Python 추상 구문 문법 트리를 처리하는 데 도움이 됩니다. 

추상 구문 자체는 Python 릴리스마다 변경될 수 있습니다. 

이 모듈은 현재 문법이 어떻게 생겼는지 프로그래밍 방식으로 알아내고 수정할 수 있도록 도와줍니다. 

추상 구문 트리는 `ast.PyCF_ONLY_AST`를 플래그로 `compile()` 내장 함수에 전달하거나 이 모듈의 `parse()` 함수를 사용하여 생성할 수 있습니다. 

결과는 모든 클래스가 `ast.AST`에서 상속되는 객체 트리가 됩니다. 

수정된 추상 구문 트리는 내장 `compile()` 함수를 사용하여 Python 코드 객체로 컴파일할 수 있습니다. 

또한 트리 작업을 더 간단하게 만드는 다양한 도우미 기능이 제공됩니다. 

도우미 함수와 일반적으로 이 모듈의 주요 의도는 파이썬 구문(예: 템플릿 엔진)과 긴밀하게 작동하는 라이브러리에 사용하기 쉬운 인터페이스를 제공하는 것입니다.

 

ast.literal_eval()

eval()은 파이썬의 내장 함수인데, literal_eval은 우리가 하려는 목적을 이루게 하면서도, 안전성까지 보장해주는 함수였다.

더 자세한 내용은 다음을 참조 하자

  • 참조에 의하면, 이것은 값을 직접 구문 분석할 필요 없이 신뢰할 수 없는 소스의 Python 값을 포함하는 문자열을 안전하게 평가하는 데 사용할 수 있습니다.
  • 예를 들어 연산자 또는 인덱싱과 관련된 임의의 복잡한 표현식을 평가할 수 없습니다.
def literal_eval(node_or_string):
    """
    Safely evaluate an expression node or a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
    sets, booleans, and None.
    표현식 노드 또는 Python 표현식이 포함된 문자열을 안전하게 평가하십시오. 
    제공된 문자열 또는 노드는 문자열, 바이트, 숫자, 튜플, 목록, 사전, 집합, 부울 및 없음과 같은 
    Python 리터럴 구조로만 구성될 수 있습니다.
    """
    if isinstance(node_or_string, str):
        node_or_string = parse(node_or_string, mode='eval')
    if isinstance(node_or_string, Expression):
        node_or_string = node_or_string.body
    def _raise_malformed_node(node):
        raise ValueError(f'malformed node or string: {node!r}')
    def _convert_num(node):
        if not isinstance(node, Constant) or type(node.value) not in (int, float, complex):
            _raise_malformed_node(node)
        return node.value
    def _convert_signed_num(node):
        if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
            operand = _convert_num(node.operand)
            if isinstance(node.op, UAdd):
                return + operand
            else:
                return - operand
        return _convert_num(node)
    def _convert(node):
        if isinstance(node, Constant):
            return node.value
        elif isinstance(node, Tuple):
            return tuple(map(_convert, node.elts))
        elif isinstance(node, List):
            return list(map(_convert, node.elts))
        elif isinstance(node, Set):
            return set(map(_convert, node.elts))
        elif (isinstance(node, Call) and isinstance(node.func, Name) and
              node.func.id == 'set' and node.args == node.keywords == []):
            return set()
        elif isinstance(node, Dict):
            if len(node.keys) != len(node.values):
                _raise_malformed_node(node)
            return dict(zip(map(_convert, node.keys),
                            map(_convert, node.values)))
        elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
            left = _convert_signed_num(node.left)
            right = _convert_num(node.right)
            if isinstance(left, (int, float)) and isinstance(right, complex):
                if isinstance(node.op, Add):
                    return left + right
                else:
                    return left - right
        return _convert_signed_num(node)
    return _convert(node_or_string)

'''
경고:
Python AST 컴파일러의 스택 깊이 제한으로 인해 충분히 크고 복잡한 문자열로 Python 인터프리터가 충돌할 수 있습니다.
잘못된 입력에 따라 ValueError, TypeError, SyntaxError, MemoryError 및 RecursionError가 발생할 수 있습니다.
'''

 

Reference

https://docs.python.org/3/library/ast.html

반응형

댓글

💲 Google Ads.