Разбор строки JSON, загруженной из CSV с использованием Pandas

Я работаю с файлами CSV, где несколько столбцов имеют простой объект json (несколько пар ключ-значение), в то время как другие столбцы нормальные. Вот пример:

name,dob,stats
john smith,1/1/1980,"{""eye_color"": ""brown"", ""height"": 160, ""weight"": 76}"
dave jones,2/2/1981,"{""eye_color"": ""blue"", ""height"": 170, ""weight"": 85}"
bob roberts,3/3/1982,"{""eye_color"": ""green"", ""height"": 180, ""weight"": 94}"

Какой самый эффективный способ анализа и разбивки столбца stats на дополнительные столбцы после использования df = pandas.read_csv('file.csv')?

Примерно через час я смог придумать только одно:

import json
stdf = df['stats'].apply(json.loads)
stlst = list(stdf)
stjson = json.dumps(stlst)
df.join(pandas.read_json(stjson))

Похоже, что я делаю это неправильно, и это довольно трудоемко, учитывая, что мне нужно регулярно делать это на трех столбцах.

Желаемый результат - объект dataframe ниже. Добавил следующие строки кода, чтобы попасть туда моим (дрянным) способом:

df = df.join(pandas.read_json(stjson))
del(df['stats'])
In [14]: df

Out[14]:
          name       dob eye_color  height  weight
0   john smith  1/1/1980     brown     160      76
1   dave jones  2/2/1981      blue     170      85
2  bob roberts  3/3/1982     green     180      94

Ответ 1

Есть немного более простой способ, но в конечном итоге вам придется вызывать json.loads. В pandas.read_csv есть понятие конвертора.

converters : dict. optional

Dict of functions for converting values in certain columns. Keys can either be integers or column labels

Итак, сначала определите свой пользовательский парсер. В этом случае должно работать следующее:

def CustomParser(data):
    import json
    j1 = json.loads(data)
    return j1

В вашем случае у вас будет что-то вроде:

df = pandas.read_csv(f1, converters={'stats':CustomParser},header=0)

Мы говорим read_csv для чтения данных стандартным способом, но для столбца статистики используйте наши пользовательские парсеры. Это сделает колонку статистики диктовкой

Отсюда мы можем использовать небольшой хак для непосредственного добавления этих столбцов за один шаг с соответствующими именами столбцов. Это будет работать только для обычных данных (объект json должен иметь 3 значения или, по крайней мере, пропущенные значения должны обрабатываться в нашем CustomParser)

df[sorted(df['stats'][0].keys())] = df['stats'].apply(pandas.Series)

С левой стороны, мы получаем новые имена столбцов из ключей элемента столбца статистики. Каждый элемент в столбце статистики является словарем. Итак, мы делаем массовое задание. С правой стороны мы разбиваем столбец "статистика", используя команду "Применить", чтобы создать фрейм данных из каждой пары ключ/значение.

Ответ 2

Я думаю, что применение json.load - хорошая идея, но оттуда вы можете просто напрямую преобразовать его в столбцы dataframe вместо того, чтобы писать/загружать его снова:

stdf = df['stats'].apply(json.loads)
pd.DataFrame(stdf.tolist()) # or stdf.apply(pd.Series)

или, альтернативно, в один шаг:

df.join(df['stats'].apply(json.loads).apply(pd.Series))

Ответ 3

Первоначальный ответ павла был очень хорошим, но в целом не правильным, потому что нет уверенности в том, что порядок расположения столбцов одинаков с левой и правой стороны последней строки. (На самом деле, похоже, что он не работает с тестовыми данными в вопросе, вместо этого он ошибочно переключает столбцы высоты и веса.)

Мы можем исправить это, убедившись, что список ключей dict на LHS отсортирован. Это работает, потому что apply в RHS автоматически сортирует по индексу, который в данном случае является списком имен столбцов.

def CustomParser(data):
  import json
  j1 = json.loads(data)
  return j1

df = pandas.read_csv(f1, converters={'stats':CustomParser},header=0)
df[sorted(df['stats'][0].keys())] = df['stats'].apply(pandas.Series)

Ответ 4

Функция json_normalize в пакете pandas.io.json помогает сделать это без использования пользовательской функции.

(при условии, что вы загружаете данные из файла)

from pandas.io.json import json_normalize
df = pd.read_csv(file_path, header=None)
stats_df = json_normalize(data['stats'].apply(ujson.loads).tolist())
stats_df.set_index(df.index, inplace=True)
df.join(stats_df)
del df.drop(df.columns[2], inplace=True)