【问题标题】:How to create padas.DataFrame from list of list of JSON如何从 JSON 列表创建 pandas.DataFrame
【发布时间】:2019-01-17 10:37:31
【问题描述】:

我有来自 CSV (gist with small sample) 的 pandas DataFrame:

|  title   |                       genres               |
--------------------------------------------------------
| %title1% |[{id: 1, name: '...'}, {id: 2, name: '...'}]|
| %title2% |[{id: 2, name: '...'}, {id: 4, name: '...'}]|
...
| %title9% |[{id: 3, name: '...'}, {id: 9, name: '...'}]|

每个title 都可以与多种类型的计数相关联(大于或大于1)。

任务是将数组从genre 列转换为列,并为每种类型放置一个(或Trues):

|  title   | genre_1 | genre_2 | genre_3 | ... | genre_9 |
---------------------------------------------------------
| %title1% |    1    |    1    |    0    | ... |    0    |
| %title2% |    1    |    0    |    0    | ... |    0    |
...
| %title9% |    0    |    0    |    1    | ... |    1    |

流派是常量集(该集中大约有 20 个项目)。

天真的方法是:

  1. 创建所有类型的集合
  2. 为每个类型创建列,用 0 填充
  3. 对于每一行,在 DataFrame 中检查某些类型是否在 genres 列中,并用 1 填充该类型的列。

这种方法看起来有点怪。

我认为 pandas 有更合适的方法。

【问题讨论】:

  • 你最初是如何得到那个结构的?
  • @roganjosh 来自 CSV 文件
  • CSV 文件不能保存 python 列表和字典。该列是一个字符串,或者您将 CSV 解析为该结构
  • @roganjosh 在 CSV 中这个值只是字符串 gist.github.com/feeeper/9c7b1e8f8a4cc262f17675ef0f6e1124

标签: python pandas dataframe


【解决方案1】:

据我所知,没有办法以矢量化方式对 Pandas 数据帧执行 JSON 反序列化。您应该能够做到这一点的一种方法是使用.iterrows(),它可以让您在一个循环中执行此操作(尽管比大多数内置的 pandas 操作要慢)。

import json

df = # ... your dataframe

for index, row in df.iterrows():
    # deserialize the JSON string
    json_data = json.loads(row['genres'])

    # add a new column for each of the genres (Pandas is okay with it being sparse)
    for genre in json_data:
        df.loc[index, genre['name']] = 1  # update the row in the df itself

df.drop(['genres'], axis=1, inplace=True)

注意空单元格用NaN 填充,而不是0——你应该使用.fillna() 来改变它。一个带有模糊相似数据框的简短示例看起来像

In [1]: import pandas as pd

In [2]: df = pd.DataFrame([{'title': 'hello', 'json': '{"foo": "bar"}'}, {'title': 'world', 'json': '{"foo": "bar", "ba
   ...: z": "boo"}'}])

In [3]: df.head()
Out[3]:
                           json  title
0                {"foo": "bar"}  hello
1  {"foo": "bar", "baz": "boo"}  world

In [4]: import json
   ...: for index, row in df.iterrows():
   ...:     data = json.loads(row['json'])
   ...:     for k, v in data.items():
   ...:         df.loc[index, k] = v
   ...: df.drop(['json'], axis=1, inplace=True)

In [5]: df.head()
Out[5]:
   title  foo  baz
0  hello  bar  NaN
1  world  bar  boo

【讨论】:

  • 我认为这行不通?不同数量的键会给你不匹配的列
  • 我将分配更新为使用 df.loc 而不是 row(这会阻止将更改保存到 df。任何丢失的键最终都会在数据框中显示为 NaN
  • 我试图找到一个没有迭代所有行的解决方案
【解决方案2】:

如果您的 csv 数据如下所示。

(我将引号添加到流派 json 的键中只是为了轻松使用 json 包。由于这不是主要问题,您可以将其作为预处理)

您必须遍历输入 DataFrame 的所有行。

for index, row in inputDf.iterrows():
    fullDataFrame = pd.concat([fullDataFrame, get_dataframe_for_a_row(row)])

在 get_dataframe_for_a_row 函数中:

  • 准备一个带有列标题和值行的DataFrame['title']
  • 添加名称通过将 id 附加到“genre_”而形成的列。
  • 给它们赋值 1

然后为每一行构建一个 DataFrame 并将它们连接到一个完整的 DataFrame 。 pd.concat() 连接从每一行获得的数据帧。 如果已经存在,将合并comumns。

最后,fullDataFrame.fillna(0) 将 NaN 替换为 0

您的最终 DataFrame 将如下所示。

这里是完整的代码:

import pandas as pd
import json

inputDf = pd.read_csv('title_genre.csv')

def labels_for_genre(a):
    a[0]['id']
    labels = []
    for i in range(0 , len(a)):
        label = 'genre'+'_'+str(a[i]['id'])
        labels.append(label)
    return labels

def get_dataframe_for_a_row(row): 
    labels = labels_for_genre(json.loads(row['genres']))
    tempDf = pd.DataFrame()
    tempDf['title'] = [row['title']]
    for label in labels:
        tempDf[label] = ['1']
    return tempDf

fullDataFrame = pd.DataFrame()
for index, row in inputDf.iterrows():
    fullDataFrame = pd.concat([fullDataFrame, get_dataframe_for_a_row(row)])
fullDataFrame = fullDataFrame.fillna(0)

【讨论】:

  • 我试图找到一个没有迭代所有行的解决方案
【解决方案3】:

没有iterrows的完整工作解决方案:

import pandas as pd
import itertools
import json

# read data
movies_df = pd.read_csv('https://gist.githubusercontent.com/feeeper/9c7b1e8f8a4cc262f17675ef0f6e1124/raw/022c0d45c660970ca55e889cd763ce37a54cc73b/example.csv', converters={ 'genres': json.loads })

# get genres for all items
all_genres_entries = list(itertools.chain.from_iterable(movies_df['genres'].values))

# create the list with unique genres
genres = list({v['id']:v for v in all_genres_entries}.values())

# fill genres columns
for genre in genres:
    movies_df['genre_{}'.format(genre['id'])] = movies_df['genres'].apply(lambda x: 1 if genre in x else 0)

【讨论】:

  • 是的,您仍在迭代“流派”。使用 iterrows 的目的是还包括标题。我认为您在输出数据框中也需要标题。
  • @darshanz 如您所见,我没有创建新的数据框 - 只是添加了新列(每种类型一个)。所以我仍然在数据框中有标题。
  • 抱歉,您没有注意到您正在写入相同的数据帧。但是您的解决方案仍然失败。你之前没有使用 itertools 的时候也是如此。
  • 请检查您的最后一行。除非您在整个数据集中拥有所有不同的genre_id,否则它将失败。
  • @darshanz 抱歉,但我不明白为什么它会失败。 genres 列表包含可以在数据集中找到的所有类型。
猜你喜欢
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
  • 2020-10-19
  • 2013-04-29
  • 1970-01-01
  • 2021-07-06
  • 2017-05-06
  • 2021-12-05
相关资源
最近更新 更多