Pandas 열 내부의 사전 / 목록을 별도의 열로 분할


147

postgreSQL 데이터베이스에 저장된 데이터가 있습니다. Python2.7을 사용 하여이 데이터를 쿼리하고 Pandas DataFrame으로 변환합니다. 그러나이 데이터 프레임의 마지막 열에는 그 안에 값의 사전 (또는 목록?)이 있습니다. DataFrame은 다음과 같습니다.

[1] df
Station ID     Pollutants
8809           {"a": "46", "b": "3", "c": "12"}
8810           {"a": "36", "b": "5", "c": "8"}
8811           {"b": "2", "c": "7"}
8812           {"c": "11"}
8813           {"a": "82", "c": "15"}

DataFrame이 다음과 같이 보이도록이 열을 별도의 열로 분할해야합니다.

[2] df2
Station ID     a      b       c
8809           46     3       12
8810           36     5       8
8811           NaN    2       7
8812           NaN    NaN     11
8813           82     NaN     15

내가 가진 주요 문제는 목록의 길이가 동일하지 않다는 것입니다. 그러나 모든 목록에는 최대 3 개의 값 (a, b 및 c) 만 포함됩니다. 그리고 그들은 항상 같은 순서로 나타납니다 (첫 번째, 두 번째, c 세 번째).

다음 코드는 올바르게 작동하고 원하는 것을 정확하게 반환하는 데 사용되었습니다 (df2).

[3] df 
[4] objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
[5] df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
[6] print(df2)

지난주 에이 코드를 실행하고 있었고 정상적으로 작동했습니다. 그러나 이제 내 코드가 깨졌고 줄 [4] 에서이 오류가 발생합니다.

IndexError: out-of-bounds on slice (end) 

코드를 변경하지 않았지만 이제 오류가 발생합니다. 나는 이것이 내 방법이 강력하지 않거나 적절하지 않기 때문이라고 생각합니다.

이 목록 열을 별도의 열로 나누는 방법에 대한 제안이나 지침은 대단히 감사하겠습니다!

편집 : .tolist () 및 .apply 메소드는 하나의 유니 코드 문자열이기 때문에 내 코드에서 작동하지 않는다고 생각합니다.

#My data format 
u{'a': '1', 'b': '2', 'c': '3'}

#and not
{u'a': '1', u'b': '2', u'c': '3'}

이 형식으로 postgreSQL 데이터베이스에서 데이터를 가져옵니다. 이 문제에 대한 도움이나 아이디어가 있습니까? 유니 코드를 변환하는 방법이 있습니까?


약간 다른 해결책으로 대답했지만 코드도 실제로 잘 작동합니다. 아래의 더미 예제를 사용하여, iloc부분을 제외하면 팬더 0.18.1을 사용하여 작동합니다.
joris

그것의 일부는 iloc[:, :3]3 개의 항목이 있다고 가정하고 더 최근의 데이터 조각에는 1 또는 2 만있을 것입니다 (예 : b와 같지 않음 index 8813)?
dwanderson

답변:


167

문자열을 실제 dict로 변환하려면을 수행하십시오 df['Pollutant Levels'].map(eval). 나중에 아래 솔루션을 사용하여 dict를 다른 열로 변환 할 수 있습니다.


작은 예제를 사용하면 다음을 사용할 수 있습니다 .apply(pd.Series).

In [2]: df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

In [3]: df
Out[3]:
   a                   b
0  1           {u'c': 1}
1  2           {u'd': 3}
2  3  {u'c': 5, u'd': 6}

In [4]: df['b'].apply(pd.Series)
Out[4]:
     c    d
0  1.0  NaN
1  NaN  3.0
2  5.0  6.0

나머지 데이터 프레임과 결합하려면 concat위의 결과를 가진 다른 열 을 사용할 수 있습니다.

In [7]: pd.concat([df.drop(['b'], axis=1), df['b'].apply(pd.Series)], axis=1)
Out[7]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

코드를 사용하면 iloc부분을 생략해도 작동합니다 .

In [15]: pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)
Out[15]:
   a    c    d
0  1  1.0  NaN
1  2  NaN  3.0
2  3  5.0  6.0

2
나는 오랫동안 사용 해 왔으며 pd.DataFrame(df[col].tolist())결코 생각하지 않았습니다 apply(pd.Series). 아주 좋아요
ayhan

1
나는 이제 문제를 깨닫는다. 전체 행이 하나의 유니 코드 문자열이기 때문에 .apply (pd.Series)가 내 데이터 세트에서 작동하지 않습니다. 'u'{ 'a': '1', 'b': '2', 'c': '3'}이 아닌 {u'a ':'1 ', u'b': '2', 귀하의 솔루션이 보여주는 것처럼 u'c ':'3 '}. 따라서 코드는 인식 가능한 3 개의 열로 분할 할 수 없습니다.
llaffin

2
@ayhan 실제로 테스트를 거쳤 DataFrame(df['col'].tolist())으며 적용 방식보다 훨씬 빠릅니다.
joris

3
@llaffin 만약 그것이 문자열이라면, 그것을 그것을 DataFrame으로 변환 df[col].map(eval)하기 전에 실제 dict로 변환 할 수 있습니다
joris

2
완벽하게 작동하지만 Lech Birek이 제공 한 새로운 솔루션 (2019)보다 훨씬 느립니다. stackoverflow.com/a/55355928/2721710
drasc

85

나는 질문이 꽤 오래되었다는 것을 알고 있지만 여기에 답을 찾고 있습니다. 실제로 다음을 사용 하여이 작업을 수행하는 더 나은 (빠른) 방법이 있습니다 json_normalize.

import pandas as pd

df2 = pd.json_normalize(df['Pollutant Levels'])

이것은 비용이 많이 드는 적용 기능을 피합니다 ...


4
와! JSON 객체의 Pandas에서 하루 종일 지루하고 혼란스러운 적용 기능을 수행 한 다음이 답변을 우연히 발견하고 "아무도 쉽지 않았습니다."라고 생각했습니다. 그런 다음 시도해 보았습니다. 정말 고마워!
Emac

여기서 유일한 문제는 json이없는 다른 열을 복사하지 않는 것입니다. 즉, 한 행의 json 값을 정규화하려고하면 복사하여 두 행을 결합해야합니다. 여전히 반복보다 훨씬 좋습니다. 방법. Cudos!
Mr.Drew

이 솔루션의 경우 정규화해야 할 열 목록을 동적으로 선택하는 방법은 무엇입니까? .json파일 에서 가져온 트랜잭션 데이터 는 다른 소스 에서 가져 오고 항상 중첩 된 동일한 열이 아닙니다. dicts를 포함하지만 열을 사용할 수없는 열 목록을 만드는 방법을 찾으려고 노력했습니다.
Callum Smyth

5
from pandas.io.json import json_normalize
Ramin Melikov

최종 열에 접두사를 적용하는 방법이 있습니까? 내가 좋아하는 인자가 나타났습니다 meta_prefix하고 record_prefix. 내 데이터 프레임으로 작업을 수행 할 수는 없지만 최종 데이터 프레임은 정확하지만 접두사를 적용하고 싶습니다.
J. Snow

21

이것을 시도하십시오 : SQL에서 반환 된 데이터는 Dict로 변환되어야합니다. 아니면 "Pollutant Levels" 지금 있을 수 있을까 Pollutants'

   StationID                   Pollutants
0       8809  {"a":"46","b":"3","c":"12"}
1       8810   {"a":"36","b":"5","c":"8"}
2       8811            {"b":"2","c":"7"}
3       8812                   {"c":"11"}
4       8813          {"a":"82","c":"15"}


df2["Pollutants"] = df2["Pollutants"].apply(lambda x : dict(eval(x)) )
df3 = df2["Pollutants"].apply(pd.Series )

    a    b   c
0   46    3  12
1   36    5   8
2  NaN    2   7
3  NaN  NaN  11
4   82  NaN  15


result = pd.concat([df, df3], axis=1).drop('Pollutants', axis=1)
result

   StationID    a    b   c
0       8809   46    3  12
1       8810   36    5   8
2       8811  NaN    2   7
3       8812  NaN  NaN  11
4       8813   82  NaN  15

13

멀린의 대답은 더 좋고 매우 쉬우나 람다 함수는 필요하지 않습니다. 사전 평가는 아래 그림과 같이 다음 두 가지 방법 중 하나로 무시해도됩니다.

방법 1 : 두 단계

# step 1: convert the `Pollutants` column to Pandas dataframe series
df_pol_ps = data_df['Pollutants'].apply(pd.Series)

df_pol_ps:
    a   b   c
0   46  3   12
1   36  5   8
2   NaN 2   7
3   NaN NaN 11
4   82  NaN 15

# step 2: concat columns `a, b, c` and drop/remove the `Pollutants` 
df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

방법 2 : 위의 두 단계를 한 번에 결합 할 수 있습니다.

df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1)

df_final:
    StationID   a   b   c
0   8809    46  3   12
1   8810    36  5   8
2   8811    NaN 2   7
3   8812    NaN NaN 11
4   8813    82  NaN 15

13

이 방법은 '오염 물질'열을 추출하는 것이 좋습니다.

df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)

그것은 훨씬 빠르다

df_pollutants = df['Pollutants'].apply(pd.Series)

df의 크기가 거대 할 때.


이것이 어떻게 / 왜 작동하는지 설명하고 훨씬 나아질 수 있다면 좋을 것입니다! 나를 위해 그것은 항상 더 빠르며 ~ 1000 행 이상을 얻으면 ~ 200 배 더 빠릅니다
Sam Mason

@SamMason 당신이 할 때 apply전체 데이터 프레임이 팬더에 의해 관리되지만이 올 때 values그것을에서만 재생되는 numpy ndarrays그것 때문에 순수 가지고 있다는 사실에 intrincicly 빠르게하는 c구현을.
Sagar Kar

8

+ join와 함께 사용할 수 있습니다 . 성능은 + 와 비교할 수 있지만 일부 구문이 더 깔끔 할 수 있습니다.poptolistconcatdroptolist

res = df.join(pd.DataFrame(df.pop('b').tolist()))

다른 방법으로 벤치마킹 :

df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]})

def joris1(df):
    return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1)

def joris2(df):
    return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1)

def jpp(df):
    return df.join(pd.DataFrame(df.pop('b').tolist()))

df = pd.concat([df]*1000, ignore_index=True)

%timeit joris1(df.copy())  # 1.33 s per loop
%timeit joris2(df.copy())  # 7.42 ms per loop
%timeit jpp(df.copy())     # 7.68 ms per loop

3

한 줄 솔루션은 다음과 같습니다.

>>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1)
>>> print(df)
   Station ID    a    b   c
0        8809   46    3  12
1        8810   36    5   8
2        8811  NaN    2   7
3        8812  NaN  NaN  11
4        8813   82  NaN  15

1

my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col'])

..는 dict을 올바르게 구문 분석하고 (각 dict 키를 별도의 df 열에, 키 값을 df 행에 넣음), dicts는 처음에 단일 열로 스 쿼지되지 않았습니다.


0

메소드에서 해당 단계를 연결했으며 확장 할 dict가 포함 된 데이터 프레임과 열만 전달하면됩니다.

def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame:
    """
    dw: DataFrame with some column which contain a dict to expand
        in columns
    column_to_expand: String with column name of dw
    """
    import pandas as pd

    def convert_to_dict(sequence: str) -> Dict:
        import json
        s = sequence
        json_acceptable_string = s.replace("'", "\"")
        d = json.loads(json_acceptable_string)
        return d    

    expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1),
                                    dw[column_to_expand]
                                    .apply(convert_to_dict)
                                    .apply(pd.Series)],
                                    axis=1)
    return expanded_dataframe

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.