【问题标题】:Normalize array of json records into dataframe将 json 记录数组规范化为数据框
【发布时间】:2021-12-15 20:23:18
【问题描述】:

我想从位于 here 的 owid covid19 json 数据创建一个数据框。 json 在数据列中有一组每日记录,这与国家索引一起是我试图制作的数据框。

{"AFG":{"continent":"Asia","location":"Afghanistan","population":39835428.0,"population_density":54.422,"median_age":18.6,"aged_65_older":2.581,"aged_70_older":1.337,"gdp_per_capita":1803.987,"cardiovasc_death_rate":597.029,"diabetes_prevalence":9.59,"handwashing_facilities":37.746,"hospital_beds_per_thousand":0.5,"life_expectancy":64.83,"human_development_index":0.511,"data":[{"date":"2020-02-24","total_cases":5.0,"new_cases":5.0,"total_cases_per_million":0.126,"new_cases_per_million":0.126,"stringency_index":8.33},{"date":"2020-02-25","total_cases":5.0,"new_cases":0.0,"total_cases_per_million":0.126,"new_cases_per_million":0.0,"stringency_index":8.33},

到目前为止,我一直将文件直接加载到数据框中

df = pd.read_json('owid-covid-data.json', orient='index')

然后对数组进行标准化

data = pd.concat([pd.DataFrame(json_normalize(key)) for key in df['data']])

除了删除索引并因此不提供标识符以连接回静态值之外,这工作正常。

我还想有一种比我使用过的更有效的标准化方法。

非常感谢任何帮助!

【问题讨论】:

  • 这不是有效的 JSON。您的第二个字符是一个左括号,这是不允许的,并且您使用的是单引号而不是双引号。这只是您数据的 Python 表示形式吗?
  • @TimRoberts 是的,抱歉,这是 Python 表示,现在更新
  • 您的数据是一个字典列表,例如[{"AFG": ...}]?

标签: python json pandas dataframe


【解决方案1】:
df = pd.read_json("https://covid.ourworldindata.org/data/owid-covid-data.json", orient='index')

# explode records/ lists into new rows, convert to dict, 
# use it to create a new DataFrame, and transpose it
data = pd.DataFrame(df['data'].explode().to_dict()).T

df = df.drop(columns='data').join(data)

性能

忽略数据读取

>>> %%timeit
... data = pd.DataFrame(df['data'].explode().to_dict()).T
... df.drop(columns='data').join(data)

84.4 ms ± 3.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

编辑 - 免责声明

上述解决方案实际上是错误的。在to_dict 转换过程中,许多记录丢失,因为有很多重复的国家代码键(系列索引),并且字典的键必须是唯一的。为了解决这个问题,首先,我们需要重置索引以确保它是唯一的。只有在新的DataFrame创建完成后,我们才添加原来的索引。

data = df['data'].explode()
data_df = pd.DataFrame(data.reset_index(drop=True).to_dict()).T
data_df.index = data.index

df = df.drop(columns='data').join(data_df)

这比之前的解决方案慢很多,因为实际上有 127314 条记录,而之前的解决方案只产生 233 条记录(唯一的国家代码)。即使我们忽略 join 部分,就像 Bruno 的解决方案一样,它也比 Bruno 的解决方案慢得多

>>> %%timeit 
... data = df['data'].explode()
... new_df = pd.DataFrame(data.reset_index(drop=True).to_dict()).T
... new_df.index = data.index

17.6 s ± 972 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# Bruno's solution 
>>> %%timeit
... country_codes = []
... datas = []
... for index, data in zip(df.index, df['data']):
...     datas.extend(data)
...     country_codes.extend(len(data)*[index])
...     
... new_df = pd.DataFrame(datas)
... new_df['country_code'] = country_codes

1.86 s ± 32.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

编辑 2 - 有更好的方法...

我找到了一个更好、更简单的解决方案。我肯定是过于复杂了。和布鲁诺的方案基本一样

data = df['data'].explode()
data_df = pd.DataFrame(data.tolist(), index=data.index)

df = df.drop(columns='data').join(data_df)

它和布鲁诺的解决方案一样快

>>> %%timeit 
... data = df['data'].explode()
... pd.DataFrame(data.tolist(), index=data.index)

1.87 s ± 16.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 如果你也跑了 bruno's 会是一个很好的比较,所以时间可以更具可比性
  • @sammywemmy 实际上我发现我的解决方案不正确。在 dict 转换期间,重复的国家/地区键和信息会丢失。我会尽快更新。
  • @sammywemmy 更新了解决方案。它实际上要慢得多......
  • 在读入 Pandas 之前,最好的性能可能在字典中。这可能意味着避免使用 pd.read_json 读取数据,而只使用可能的请求
  • @sammywemmy 我实际上找到了一个更简单的解决方案。我同意你的看法。可能我会使用json 模块读取数据并将其传递给pd.json_normalize 以使其一次正常化。
【解决方案2】:

您可以使用pd.json_normalize(),它必须更快(我在整个 JSON 文件上都试过了):

%%timeit
pd.json_normalize(
    data["AFG"],
    record_path=["data"],
    meta=[
        "continent",
        "location",
        "population",
        "population_density",
        "median_age",
        "aged_65_older",
        "aged_70_older",
        "gdp_per_capita",
        "cardiovasc_death_rate",
        "diabetes_prevalence",
        "handwashing_facilities",
        "hospital_beds_per_thousand",
        "life_expectancy",
        "human_development_index",
    ],
)

17.9 ms ± 1.68 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

输出:

 date  total_cases  new_cases  total_cases_per_million  new_cases_per_million  stringency_index  new_cases_smoothed  ...  gdp_per_capita  cardiovasc_death_rate  diabetes_prevalence  handwashing_facilities  hospital_beds_per_thousand  life_expectancy  human_development_index
0    2020-02-24          5.0        5.0                    0.126                  0.126              8.33                 NaN  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
1    2020-02-25          5.0        0.0                    0.126                  0.000              8.33                 NaN  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
2    2020-02-26          5.0        0.0                    0.126                  0.000              8.33                 NaN  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
3    2020-02-27          5.0        0.0                    0.126                  0.000              8.33                 NaN  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
4    2020-02-28          5.0        0.0                    0.126                  0.000              8.33                 NaN  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
..          ...          ...        ...                      ...                    ...               ...                 ...  ...             ...                    ...                  ...                     ...                         ...              ...                      ...
610  2021-10-26     156071.0       31.0                 3917.894                  0.778               NaN              38.571  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
611  2021-10-27     156124.0       53.0                 3919.225                  1.330               NaN              37.857  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
612  2021-10-28     156166.0       42.0                 3920.279                  1.054               NaN              39.286  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
613  2021-10-29     156196.0       30.0                 3921.032                  0.753               NaN              37.857  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511
614  2021-10-30     156210.0       14.0                 3921.384                  0.351               NaN              38.571  ...        1803.987                597.029                 9.59                  37.746                         0.5            64.83                    0.511

[615 rows x 38 columns]

但最快的解决方案就是以csv 格式下载数据,因为他们会为您提供链接here

【讨论】:

  • 在执行pd.json_noramlize之前你是怎么加载json文件的?
  • @Rabinzel 我向link 发出了 GET 请求。
  • 谢谢。既然你在这里很有名气,我想你可以帮帮我。如果我自己有一个问题但与这篇文章非常相关并且没有太大不同,那么stackoverflow的一般方法是什么。我想以不同的方式解决 OP 的问题,但我无法弄清楚。我是在 cmets 中提问还是通过此帖子的链接开始一个新主题?
  • @Rabinzel 如果它非常相似,它可能会被标记为重复。如果您想解决它,那么您将不得不提出解决方案,没有人可以帮助您。
【解决方案3】:

这不是最有效的方法,但确实有效:

new_df = pd.DataFrame()
for index, row in df.iterrows():
    tmp = pd.json_normalize(row['data'])
    tmp['country_code'] = index
    new_df = pd.concat([new_df, tmp])

编辑:

我找到了一种更有效的方法,即一次标准化所有 JSON:

country_codes = []
datas = []
for index, data in zip(df.index, df['data']):
    datas.extend(data)
    country_codes.extend(len(data)*[index])
    
new_df = pd.DataFrame(datas)
new_df['country_code'] = country_codes

9.38 s ± 856 ms per loop 改进为1.37 s ± 12 ms per loop

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 2021-11-13
    • 2020-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多