Pandas Sütunu içindeki sözlüğü / listeyi Ayrı Sütunlara bölme


148

PostgreSQL veritabanına kaydedilmiş verilerim var. Bu verileri Python2.7 kullanarak sorguluyorum ve bir Pandas DataFrame'e dönüştürüyorum. Bununla birlikte, bu veri çerçevesinin son sütununun içinde değerler sözlüğü (veya listesi?) Vardır. DataFrame şuna benzer:

[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'in şöyle görünmesi için bu sütunu ayrı sütunlara bölmem gerekiyor:

[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

Yaşadığım en büyük sorun, listelerin aynı uzunlukta olmaması. Ancak tüm listeler yalnızca aynı 3 değeri içerir: a, b ve c. Ve her zaman aynı sırada görünürler (a birinci, b ikinci, c üçüncü).

Aşağıdaki kod, çalışmak ve tam olarak istediğim şeyi döndürmek için KULLANILMIŞTIR (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)

Bu kodu daha geçen hafta çalıştırıyordum ve iyi çalışıyordu. Ama şimdi kodum bozuldu ve bu hatayı [4] satırından alıyorum:

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

Kodda hiçbir değişiklik yapmadım ancak şimdi hatayı alıyorum. Bunun yöntemimin sağlam veya uygun olmamasından kaynaklandığını hissediyorum.

Bu liste sütununun ayrı sütunlara nasıl bölüneceğine dair herhangi bir öneri veya rehberlik çok takdir edilecektir!

DÜZENLEME: .tolist () ve .apply yöntemlerinin kodum üzerinde çalışmadığını düşünüyorum çünkü bu bir unicode dizesi, yani:

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

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

Veriler, postgreSQL veritabanından bu biçimde içe aktarılır. Bu sorunla ilgili herhangi bir yardım veya fikir var mı? unicode'u dönüştürmenin bir yolu var mı?


Biraz farklı bir çözümle cevap verdim, ancak kodunuz da gerçekten iyi çalışmalı. Aşağıdaki kukla örneğimi kullanarak, bu, ilocparçayı dışarıda bırakırsam pandalar 0.18.1 kullanarak çalışır
joris

Bunun bir parçası olarak, iloc[:, :3]3 öğe olacağı ve belki daha yeni veri dilimlerinde sadece 1 veya 2 olacağı varsayılıyor mu (örneğin, içinde bbenzeri yok index 8813)?
dwanderson

Yanıtlar:


170

Dizeyi gerçek bir dikteye dönüştürmek için yapabilirsiniz df['Pollutant Levels'].map(eval). Daha sonra, aşağıdaki çözüm, dikteyi farklı sütunlara dönüştürmek için kullanılabilir.


Küçük bir örnek kullanarak şunları kullanabilirsiniz .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

Veri çerçevesinin geri kalanıyla birleştirmek için concat, yukarıdaki sonuçla diğer sütunları yapabilirsiniz :

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

Kodunuzu kullanarak, ilocparçayı dışarıda bırakırsam da işe yarar :

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
Ben kullanarak oldum pd.DataFrame(df[col].tolist())asla düşündüm, uzun süre apply(pd.Series). Çok hoş.
ayhan

1
Şimdi sorunun farkındayım. .Apply (pd.Series) veri kümemde çalışmıyor çünkü satırın tamamı bir unicode dizesi. Bu: u '{' a ':' 1 ',' b ':' 2 ',' c ':' 3 '} ve {u'a': '1', u'b ':' 2 'değil, u'c ':' 3 '} çözümlerinizin gösterdiği gibi. Dolayısıyla kod, onu tanınabilir 3 sütuna bölemez.
llaffin

2
@ayhan Aslında, test etti ve DataFrame(df['col'].tolist())yaklaşım uygulama yaklaşımından biraz daha hızlı!
joris

3
@llaffin Eğer bu df[col].map(eval)bir dizeyse, bunu bir DataFrame'e dönüştürmeden önce gerçek bir dikteye dönüştürebilirsiniz
joris

2
Mükemmel çalışır, ancak Lech Birek tarafından katkıda yeni çözümün (2019) daha yavaş (çok) olduğu stackoverflow.com/a/55355928/2721710
drasc

85

Sorunun oldukça eski olduğunu biliyorum, ama cevaplar için buraya geldim. Bunu kullanarak yapmanın aslında daha iyi (ve daha hızlı) bir yolu var json_normalize:

import pandas as pd

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

Bu, maliyetli uygulama işlevlerini önler ...


4
Vaov! Pandas'ta JSON nesnelerine tüm gün boyunca sıkıcı ve kafa karıştırıcı uygulama işlevleri yapıyorum ve sonra bu yanıta rastladım ve "Olmaz, bu kadar kolay olamazdı!" Diye düşündüm. Sonra denedim ve oldu. Çok teşekkürler!
Emac

Buradaki tek sorun, json olmadan diğer sütunların üzerine kopyalıyormuş gibi görünmemesi, yani bir satır json değerlerini normalleştirmeye çalışıyorsanız, onu kopyalamanız ve ikisini birleştirmeniz gerekecek, yine de yinelememden çok daha iyi yöntem. Cudos!
Bay Drew

bu çözüm için, normalleştirilmesi gereken sütunların listesini dinamik olarak seçmek nasıl mümkün olabilir? Getirdiğim işlem verileri.jsonDosyalardan farklı kaynaklardan geliyor ve her zaman iç içe geçmiş sütunlar değil. Diktler içeren ancak bunu çözemeyen bir sütun listesi oluşturmanın bir yolunu bulmaya çalışıyorum
Callum Smyth

5
from pandas.io.json import json_normalize
Ramin Melikov

Son sütunlara bir önek uygulamanın bir yolu var mı? Ben gibi argümanlar vardır fark ettik meta_prefixve record_prefix. Bununla birlikte, bunu veri çerçevemle yapamıyorum (son veri çerçevesi benim durumumda doğru ancak önekleri uygulamak istiyorum).
J. Snow

21

Şunu deneyin: SQL'den döndürülen verilerin bir Dict'e dönüştürülmesi gerekir. ya "Pollutant Levels" da şimdi olabilir mi 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

Merlin'in cevabı daha iyi ve çok kolay, ancak bir lambda fonksiyonuna ihtiyacımız yok. Sözlüğün değerlendirilmesi, aşağıda gösterildiği gibi aşağıdaki iki yoldan biriyle güvenle göz ardı edilebilir:

Yol 1: İki adım

# 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

Yol 2: Yukarıdaki iki adım tek seferde birleştirilebilir:

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

Yöntemin 'Kirleticiler' sütununu çıkarmasını şiddetle tavsiye ederim:

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

şundan çok daha hızlı

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

df boyutu dev olduğunda.


Bunun nasıl / neden çalıştığını ve çok daha iyi olduğunu açıklayabilirseniz harika olur! benim için her zaman daha hızlı ve ~ 1000'den fazla satır aldığınızda ~ 200 kat daha hızlı
Sam Mason

@SamMason apply, tüm veri çerçevesini yaptığınızda pandalar tarafından yönetilir, ancak söz konusu olduğunda valuesyalnızca numpy ndarrays, saf cuygulamalara sahip olduğu için doğal olarak daha hızlı olan ile oynar .
Sagar Kar

8

Sen kullanabilirsiniz joinile pop+ tolist. Performans + concatile karşılaştırılabilir , ancak bazıları bu sözdizimini daha temiz bulabilir:droptolist

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

Diğer yöntemlerle kıyaslama:

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

Bir satır çözüm şu şekildedir:

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

.. dikteyi düzgün bir şekilde ayrıştırırdı (her bir dikt anahtarını ayrı bir df sütununa ve anahtar değerlerini df satırlarına koyarak), böylece diktler ilk etapta tek bir sütuna sıkıştırılmazdı.


0

Bu adımları bir yöntemde birleştirdim, yalnızca veri çerçevesini ve genişletme emrini içeren sütunu iletmelisiniz:

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

-1
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.