web-dev-qa-db-fra.com

Fractionner dictionnaire / liste à l'intérieur d'une Pandas Colonne en colonnes séparées

J'ai des données sauvegardées dans une base de données postgreSQL. J'interroge ces données à l'aide de Python2.7 et les transforme en Pandas DataFrame. Cependant, la dernière colonne de cette image de données contient un dictionnaire (ou une liste?) De valeurs. Le DataFrame ressemble à ceci:

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

J'ai besoin de diviser cette colonne en colonnes séparées pour que le DataFrame ressemble à ceci:

[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

Mon problème majeur est que les listes ne sont pas de la même longueur. Mais toutes les listes ne contiennent que les 3 mêmes valeurs: a, b et c. Et ils apparaissent toujours dans le même ordre (une première, une seconde, une troisième).

Le code suivant utilisé pour travailler et retourner exactement ce que je voulais (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)

J'exécutais ce code la semaine dernière et tout fonctionnait bien. Mais maintenant mon code est cassé et j'obtiens cette erreur de la ligne [4]:

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

Je n’ai apporté aucune modification au code, mais je reçois maintenant l’erreur. Je pense que cela est dû à ma méthode qui n'est ni robuste ni appropriée.

Toutes suggestions ou conseils sur la manière de diviser cette colonne de listes en colonnes séparées seraient super appréciés!

EDIT: Je pense que les méthodes .tolist () et .apply ne fonctionnent pas sur mon code car il s'agit d'une chaîne unicode, c'est-à-dire:

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

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

Les données importent de la base de données postgreSQL dans ce format. Toute aide ou idées avec ce problème? existe-t-il un moyen de convertir l'unicode?

75
llaffin

Pour convertir la chaîne en dictée réelle, vous pouvez utiliser df['Pollutant Levels'].map(eval). Ensuite, la solution ci-dessous peut être utilisée pour convertir le dict en différentes colonnes.


En utilisant un petit exemple, vous pouvez utiliser .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

Pour le combiner avec le reste du cadre de données, vous pouvez concat les autres colonnes avec le résultat ci-dessus:

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

En utilisant votre code, cela fonctionne aussi si je laisse de côté la partie 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
101
joris

Essayez ceci: Les données renvoyées par SQL doivent être converties en un Dict. ou pourrait-il s'agir de "Pollutant Levels" est maintenant 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
16
Merlin

Je sais que la question est assez ancienne, mais je suis arrivé ici pour chercher des réponses. Il existe actuellement un moyen plus efficace (et plus rapide) d'utiliser json_normalize:

import pandas as pd
from pandas.io.json import json_normalize

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

Cela évite des fonctions d'application coûteuses ...

11
Lech Birek

La réponse de Merlin est meilleure et très facile, mais nous n’avons pas besoin d’une fonction lambda. L'évaluation du dictionnaire peut être ignorée en toute sécurité de l'une des deux manières suivantes, comme illustré ci-dessous:

Voie 1: Deux étapes

# 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

Voie 2: Les deux étapes ci-dessus peuvent être combinées en une fois:

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
9
Hafizur Rahman

Vous pouvez utiliser join avec pop + tolist. Les performances sont comparables à concat avec drop + tolist, mais certains trouveront ce nettoyeur de syntaxe:

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

Analyse comparative avec d'autres méthodes:

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

Je recommande fortement la méthode extraire la colonne 'Pollutants':

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

c'est beaucoup plus rapide que

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

quand la taille de df est géante.

4
user9815968