Membagi kamus / daftar di dalam Kolom Pandas menjadi Kolom Terpisah

146

Saya memiliki data yang disimpan dalam database postgreSQL. Saya menanyakan data ini menggunakan Python2.7 dan mengubahnya menjadi Pandas DataFrame. Namun, kolom terakhir dari kerangka data ini memiliki kamus (atau daftar?) Dari nilai-nilai di dalamnya. DataFrame terlihat seperti ini:

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

Saya perlu membagi kolom ini menjadi kolom terpisah sehingga DataFrame terlihat seperti ini:

[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

Masalah utama yang saya alami adalah bahwa daftar itu tidak sama panjangnya. Tetapi semua daftar hanya berisi hingga 3 nilai yang sama: a, b, dan c. Dan mereka selalu muncul dalam urutan yang sama (yang pertama, b detik, c ketiga).

Kode berikut DIGUNAKAN untuk bekerja dan mengembalikan apa yang saya inginkan (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)

Saya menjalankan kode ini minggu lalu dan berfungsi dengan baik. Tapi sekarang kode saya rusak dan saya mendapatkan kesalahan ini dari baris [4]:

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

Saya tidak membuat perubahan pada kode tetapi sekarang saya mendapatkan kesalahan. Saya merasa ini karena metode saya tidak kuat atau tidak tepat.

Setiap saran atau panduan tentang cara membagi kolom daftar ini menjadi kolom terpisah akan sangat dihargai!

EDIT: Saya pikir metode .tolist () dan .apply tidak bekerja pada kode saya karena itu adalah satu string unicode, yaitu:

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

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

Data mengimpor dari database postgreSQL dalam format ini. Adakah bantuan atau ide dengan masalah ini? apakah ada cara untuk mengubah unicode?

llaffin
sumber
Saya menjawab dengan solusi yang sedikit berbeda, tetapi, kode Anda seharusnya juga berfungsi dengan baik. Menggunakan contoh boneka saya di bawah, ini berfungsi menggunakan panda 0.18.1 jika saya meninggalkan ilocbagian
joris
Apakah bagian dari itu dengan iloc[:, :3]asumsi akan ada 3 item, dan mungkin irisan data yang lebih baru hanya memiliki 1 atau 2 (misalnya tidak ada bsuka dalam index 8813)?
dwanderson

Jawaban:

166

Untuk mengonversi string menjadi dict yang sebenarnya, Anda dapat melakukannya df['Pollutant Levels'].map(eval). Setelah itu, solusi di bawah ini dapat digunakan untuk mengubah dict ke kolom yang berbeda.


Menggunakan contoh kecil, Anda dapat menggunakan .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

Untuk menggabungkannya dengan sisa kerangka data, Anda bisa concatkolom lainnya dengan hasil di atas:

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

Menggunakan kode Anda, ini juga berfungsi jika saya meninggalkan ilocbagian:

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
Joris
sumber
2
Saya telah menggunakan pd.DataFrame(df[col].tolist())untuk waktu yang lama, tidak pernah memikirkan apply(pd.Series). Sangat bagus.
ayhan 6-16
1
Saya sekarang menyadari masalahnya. The .apply (pd.Series) tidak berfungsi pada dataset saya karena seluruh baris adalah satu string unicode. Ini: u '{' a ':' 1 ',' b ':' 2 ',' c ':' 3 '} dan bukan {u'a': '1', u'b ':' 2 ', u'c ':' 3 '} seperti yang ditunjukkan oleh solusi Anda. Jadi kode tidak dapat membaginya menjadi 3 kolom yang dapat dikenali.
llaffin
2
@ayhan Sebenarnya, mengujinya, dan DataFrame(df['col'].tolist())pendekatannya lebih cepat daripada pendekatan yang berlaku!
joris
3
@llaffin Jika string, Anda dapat mengkonversi bahwa untuk sebuah dict sebenarnya dengan df[col].map(eval)sebelum mengubahnya menjadi DataFrame
Joris
2
Berfungsi sempurna, tetapi jauh lebih lambat daripada solusi baru (2019) disumbangkan oleh Lech Birek stackoverflow.com/a/55355928/2721710
drasc
85

Saya tahu pertanyaannya sudah cukup lama, tetapi saya sampai di sini mencari jawaban. Sebenarnya ada cara yang lebih baik (dan lebih cepat) sekarang untuk melakukan ini menggunakan json_normalize:

import pandas as pd

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

Ini menghindari fungsi yang mahal ...

Lech Birek
sumber
4
Wow! Saya telah melakukan fungsi-fungsi aplikasi yang membosankan dan membingungkan sepanjang hari di Panda pada objek JSON, dan kemudian saya menemukan jawaban ini dan berpikir, "Tidak mungkin, tidak semudah itu!" Lalu saya mencobanya dan ternyata itu. Terima kasih banyak!
Emac
Satu-satunya masalah di sini adalah bahwa tampaknya tidak menyalin di kolom lain tanpa json, artinya jika Anda mencoba untuk menormalkan satu baris nilai json Anda harus menyalinnya dan menggabungkan keduanya, masih jauh lebih baik daripada saya berulang metode. Cudos!
Mr.Drew
untuk solusi ini bagaimana mungkin untuk secara dinamis memilih daftar kolom mana yang perlu dinormalisasi? Data transaksional yang saya bawa dari .jsonfile berasal dari sumber yang berbeda dan tidak selalu kolom yang sama yang bersarang. Saya telah mencoba menemukan cara untuk membuat daftar kolom yang berisi dicts tetapi sepertinya tidak bisa mengatasinya
Callum Smyth
5
from pandas.io.json import json_normalize
Ramin Melikov
Apakah ada cara untuk menerapkan awalan ke kolom akhir? Saya perhatikan ada argumen seperti meta_prefixdan record_prefix. Meskipun, saya tidak bisa membuatnya bekerja dengan kerangka data saya (kerangka data akhir benar dalam kasus saya, tetapi saya ingin menerapkan awalan).
J. Snow
21

Coba ini: Data yang dikembalikan dari SQL harus diubah menjadi Dict. atau mungkinkah "Pollutant Levels" sekarangPollutants'

   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
Merlin
sumber
13

Jawaban Merlin lebih baik dan sangat mudah, tetapi kita tidak membutuhkan fungsi lambda. Evaluasi kamus dapat diabaikan dengan aman dengan salah satu dari dua cara berikut seperti yang diilustrasikan di bawah ini:

Cara 1: Dua langkah

# 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

Cara 2: Dua langkah di atas dapat digabungkan dalam sekali jalan:

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
Hafizur Rahman
sumber
13

Saya sangat merekomendasikan metode mengekstrak kolom 'Polutan':

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

jauh lebih cepat daripada

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

ketika ukuran df adalah raksasa.

pengguna9815968
sumber
akan lebih bagus jika Anda bisa menjelaskan bagaimana / mengapa ini bekerja dan jauh lebih baik! bagi saya itu selalu lebih cepat, dan ~ 200 kali lebih cepat setelah Anda mendapatkan lebih dari ~ 1000 baris
Sam Mason
@SamMason ketika Anda melakukan applyseluruh bingkai data dikelola oleh panda, tetapi ketika sampai pada valueshal itu hanya bermain dengan numpy ndarraysyang secara intrincicly lebih cepat karena fakta bahwa ia memiliki cimplementasi murni .
Sagar Kar
8

Anda dapat menggunakan joindengan pop+ tolist. Performa sebanding dengan concatdengan drop+ tolist, tetapi beberapa mungkin menemukan ini pembersih sintaks:

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

Benchmarking dengan metode lain:

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
jpp
sumber
3

Solusi satu baris adalah sebagai berikut:

>>> 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
Jaroslav Bezděk
sumber
1

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

.. akan mem-parsing dict dengan benar (menempatkan setiap kunci dict ke dalam kolom df terpisah, dan nilai-nilai kunci ke dalam baris df), sehingga dict tidak akan terjepit ke dalam satu kolom di tempat pertama.

mirekphd
sumber
0

Saya telah menyatukan langkah-langkah tersebut dalam suatu metode, Anda harus melewati hanya kerangka data dan kolom yang berisi dict untuk diperluas:

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
Emanuel Fontelles
sumber
-1
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
Siraj S.
sumber