web-dev-qa-db-fra.com

Comment aplatir un pandas dataframe avec certaines colonnes comme json?

J'ai un dataframe df qui charge les données d'une base de données. La plupart des colonnes sont des chaînes json tandis que certaines sont même une liste de jsons. Par exemple:

id     name     columnA                               columnB
1     John     {"dist": "600", "time": "0:12.10"}    [{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "3rd", "value": "200"}, {"pos": "total", "value": "1000"}]
2     Mike     {"dist": "600"}                       [{"pos": "1st", "value": "500"},{"pos": "2nd", "value": "300"},{"pos": "total", "value": "800"}]
...

Comme vous pouvez le voir, toutes les lignes n'ont pas le même nombre d'éléments dans les chaînes json pour une colonne.

Ce que je dois faire, c'est garder les colonnes normales comme id et name telles quelles et aplatir les colonnes json comme ceci:

id    name   columnA.dist   columnA.time   columnB.pos.1st   columnB.pos.2nd   columnB.pos.3rd     columnB.pos.total
1     John   600            0:12.10        500               300               200                 1000 
2     Mark   600            NaN            500               300               Nan                 800 

J'ai essayé d'utiliser json_normalize ainsi:

from pandas.io.json import json_normalize
json_normalize(df)

Mais il semble y avoir quelques problèmes avec keyerror. Quelle est la bonne façon de procéder?

21
sfactor

Voici une solution utilisant json_normalize() à nouveau en utilisant une fonction personnalisée pour obtenir les données dans le format correct compris par la fonction json_normalize.

import ast
from pandas.io.json import json_normalize

def only_dict(d):
    '''
    Convert json string representation of dictionary to a python dict
    '''
    return ast.literal_eval(d)

def list_of_dicts(ld):
    '''
    Create a mapping of the tuples formed after 
    converting json strings of list to a python list   
    '''
    return dict([(list(d.values())[1], list(d.values())[0]) for d in ast.literal_eval(ld)])

A = json_normalize(df['columnA'].apply(only_dict).tolist()).add_prefix('columnA.')
B = json_normalize(df['columnB'].apply(list_of_dicts).tolist()).add_prefix('columnB.pos.') 

Enfin, joignez le DFs sur l'index commun pour obtenir:

df[['id', 'name']].join([A, B])

Image


EDIT: - Selon le commentaire de @MartijnPieters, la façon recommandée de décoder les chaînes json serait d'utiliser json.loads() qui est beaucoup plus rapide par rapport à l'utilisation ast.literal_eval() si vous savez que la source de données est JSON.

21
Nickil Maveli

créer une fonction personnalisée pour aplatir columnB puis utiliser pd.concat

def flatten(js):
    return pd.DataFrame(js).set_index('pos').squeeze()

pd.concat([df.drop(['columnA', 'columnB'], axis=1),
           df.columnA.apply(pd.Series),
           df.columnB.apply(flatten)], axis=1)

enter image description here

8
piRSquared

Le plus rapide semble être:

json_struct = json.loads(df.to_json(orient="records"))
df_flat = pf.io.json.json_normalize(json_struct)
0
staonas