Tách từ điển / danh sách bên trong Cột Pandas thành các Cột riêng biệt


146

Tôi có dữ liệu được lưu trong cơ sở dữ liệu postgreSQL. Tôi đang truy vấn dữ liệu này bằng Python2.7 và biến nó thành Pandas DataFrame. Tuy nhiên, cột cuối cùng của khung dữ liệu này có một từ điển (hoặc danh sách?) Các giá trị bên trong nó. DataFrame trông như thế này:

[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"}

Tôi cần chia cột này thành các cột riêng biệt để DataFrame trông như thế này:

[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

Vấn đề chính tôi gặp phải là các danh sách không có cùng độ dài. Nhưng tất cả các danh sách chỉ chứa tối đa 3 giá trị: a, b và c. Và chúng luôn xuất hiện theo cùng một thứ tự (a thứ nhất, b giây, c thứ ba).

Đoạn mã sau được sử dụng để hoạt động và trả về chính xác những gì tôi muốn (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)

Tôi đã chạy mã này chỉ tuần trước và nó đã hoạt động tốt. Nhưng bây giờ mã của tôi bị hỏng và tôi gặp lỗi này từ dòng [4]:

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

Tôi đã không thay đổi mã nhưng hiện đang nhận được lỗi. Tôi cảm thấy điều này là do phương pháp của tôi không mạnh mẽ hoặc đúng đắn.

Bất kỳ đề xuất hoặc hướng dẫn nào về cách chia cột danh sách này thành các cột riêng biệt sẽ được đánh giá cao!

EDIT: Tôi nghĩ rằng các phương thức .tolist () và .apply không hoạt động trên mã của tôi vì đó là một chuỗi unicode, tức là:

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

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

Dữ liệu được nhập từ cơ sở dữ liệu postgreSQL ở định dạng này. Bất kỳ trợ giúp hoặc ý tưởng với vấn đề này? Có cách nào để chuyển đổi unicode?


Tôi đã trả lời với một giải pháp hơi khác, nhưng, mã của bạn thực sự cũng sẽ hoạt động tốt. Sử dụng ví dụ giả của tôi dưới đây, điều này hoạt động bằng cách sử dụng gấu trúc 0.18.1 nếu tôi rời khỏi ilocphần
joris

Có phải một phần của iloc[:, :3]giả định rằng sẽ có 3 mục và có thể các lát dữ liệu gần đây chỉ có 1 hoặc 2 (ví dụ: có xảy ra không bgiống như trong index 8813) không?
lùn

Câu trả lời:


166

Để chuyển đổi chuỗi thành một dict thực tế, bạn có thể làm df['Pollutant Levels'].map(eval). Sau đó, giải pháp dưới đây có thể được sử dụng để chuyển đổi lệnh thành các cột khác nhau.


Sử dụng một ví dụ nhỏ, bạn có thể sử dụng .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

Để kết hợp nó với phần còn lại của khung dữ liệu, bạn có thể concatcác cột khác với kết quả trên:

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

Sử dụng mã của bạn, điều này cũng hoạt động nếu tôi bỏ qua ilocphần:

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
Tôi đã sử dụng pd.DataFrame(df[col].tolist())trong một thời gian dài, không bao giờ nghĩ về apply(pd.Series). Rất đẹp.
ayhan

1
Bây giờ tôi nhận ra vấn đề. .Pyly (pd.Series) không hoạt động trên tập dữ liệu của tôi vì toàn bộ hàng là một chuỗi unicode. Đó là: u '{' a ':' 1 ',' b ':' 2 ',' c ':' 3 '} chứ không phải {u'a': '1', u'b ':' 2 ', u'c ':' 3 '} như các giải pháp của bạn hiển thị. Vì vậy, mã không thể chia nó thành 3 cột dễ nhận biết.
llaffin

2
@ayhan Trên thực tế, đã thử nghiệm nó, và DataFrame(df['col'].tolist())cách tiếp cận khá nhanh hơn so với phương pháp áp dụng!
joris

3
@llaffin Nếu đó là một chuỗi, bạn có thể chuyển đổi chuỗi đó thành một lệnh thực tế df[col].map(eval)trước khi chuyển đổi nó thành DataFrame
Joris

2
Hoạt động hoàn hảo, nhưng chậm hơn nhiều so với giải pháp mới (2019) được đóng góp bởi Lech Birek stackoverflow.com/a/55355928/2721710
drasc

85

Tôi biết câu hỏi khá cũ, nhưng tôi đã đến đây để tìm kiếm câu trả lời. Thực sự có một cách tốt hơn (và nhanh hơn) để thực hiện việc này bằng cách sử dụng json_normalize:

import pandas as pd

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

Điều này tránh các chức năng áp dụng tốn kém ...


4
Ồ Tôi đã làm các chức năng áp dụng tẻ nhạt và khó hiểu cả ngày trong Pandas trên các đối tượng JSON, và sau đó tôi tình cờ nhận được câu trả lời này và nghĩ rằng "Không thể nào, nó không thể dễ dàng đến thế!" Sau đó, tôi đã thử nó và nó được. Cám ơn rất nhiều!
Emac

Vấn đề duy nhất ở đây là nó dường như không sao chép trên các cột khác mà không có json, nghĩa là nếu bạn đang cố gắng bình thường hóa một hàng giá trị json, bạn sẽ phải sao chép nó và kết hợp cả hai, vẫn tốt hơn nhiều so với phép lặp của tôi phương pháp. Cudos!
Mr.Drew

Đối với giải pháp này, làm thế nào để có thể tự động chọn danh sách các cột cần chuẩn hóa? Dữ liệu giao dịch mà tôi mang đến từ .jsoncác tệp đến từ các nguồn khác nhau và không phải lúc nào các cột đó cũng được lồng vào nhau. Tôi đã cố gắng tìm cách tạo một danh sách các cột có chứa các dấu hiệu nhưng dường như không thể giải quyết được
Callum Smyth

5
from pandas.io.json import json_normalize
Ramin Melikov

Có cách nào để áp dụng tiền tố cho các cột cuối cùng không? Tôi đã nhận thấy có những tranh luận như meta_prefixrecord_prefix. Mặc dù, tôi không thể làm cho nó hoạt động với khung dữ liệu của mình (khung dữ liệu cuối cùng là chính xác trong trường hợp của tôi nhưng tôi muốn áp dụng các tiền tố).
J. Snow

21

Hãy thử điều này: Dữ liệu được trả về từ SQL phải được chuyển đổi thành Dict. hoặc có thể "Pollutant Levels" là bây giờ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

Câu trả lời của Merlin tốt hơn và siêu dễ, nhưng chúng ta không cần chức năng lambda. Việc đánh giá từ điển có thể được bỏ qua một cách an toàn theo một trong hai cách sau như minh họa dưới đây:

Cách 1: Hai bước

# 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

Cách 2: Hai bước trên có thể được kết hợp trong một lần:

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

12

Tôi thực sự khuyên bạn nên sử dụng phương pháp trích xuất cột 'Chất ô nhiễm':

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

nó nhanh hơn nhiều

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

khi kích thước của df là khổng lồ.


sẽ là tuyệt vời nếu bạn có thể giải thích làm thế nào / tại sao điều này hoạt động và tốt hơn nhiều! đối với tôi nó luôn luôn nhanh hơn và nhanh hơn ~ 200 lần khi bạn nhận được hơn ~ 1000 hàng
Sam Mason

@SamMason khi bạn thực hiện applytoàn bộ khung dữ liệu được quản lý bởi gấu trúc, nhưng khi nói đến valuesnó chỉ chơi với numpy ndarraystốc độ thực sự nhanh hơn do thực tế là nó có các ctriển khai thuần túy .
Sagar Kar

8

Bạn có thể sử dụng joinvới pop+ tolist. Hiệu suất tương đương concatvới drop+ tolist, nhưng một số có thể tìm thấy trình dọn dẹp cú pháp này:

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

Điểm chuẩn với các phương pháp khác:

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

Giải pháp một dòng là như sau:

>>> 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'])

.. sẽ phân tích cú pháp chính xác (đặt từng khóa dict vào một cột df riêng và các giá trị khóa thành các hàng df), do đó, các ký tự sẽ không bị nén vào một cột ở vị trí đầu tiên.


0

Tôi đã nối các bước đó trong một phương thức, bạn chỉ phải truyền khung dữ liệu và cột chứa lệnh để mở rộng:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.