【问题标题】:Parsing a JSON string which was loaded from a CSV using Pandas解析使用 Pandas 从 CSV 加载的 JSON 字符串
【发布时间】:2014-01-07 22:29:55
【问题描述】:

我正在使用 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}"

使用df = pandas.read_csv('file.csv') 之后,将stats 列解析并拆分为其他列的最有效方法是什么?

大约一个小时后,我唯一能想到的就是:

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

这似乎是我做错了,考虑到我需要定期在三列上执行此操作,这是一项相当多的工作。

所需的输出是下面的数据框对象。添加了以下代码行以我(糟糕)的方式到达那里:

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

【问题讨论】:

    标签: python pandas


    【解决方案1】:
    • 如果您的 .csv 文件中有 DateTime 值,df[sorted(df['stats'][0].keys())] = df['stats'].apply(pandas.Series) 会弄乱日期时间值
    • link 提供了一些如何读取 csv 文件的提示 将 json 字符串放入数据框中。

    您可以执行以下操作来读取带有 json 字符串列的 csv 文件并将您的 json 字符串转换为列。

    1. 将您的 csv 读入数据框 (read_df)

      read_df = pd.read_csv('yourFile.csv', converters={'state':json.loads}, header=0, quotechar="'")

    2. 将json字符串列转换为新的数据框

      state_df = read_df['state'].apply(pd.Series)

    3. 将 2 数据帧与索引号合并。

      df = pd.merge(read_df, state_df, left_index=True, right_index=True)

    【讨论】:

      【解决方案2】:

      选项 1

      如果您在将列写入 csv 之前使用 json.dumps 转储了该列,则可以使用以下命令将其读回:

      import json
      import pandas as pd
      
      df = pd.read_csv('data/file.csv', converters={'json_column_name': json.loads})
      

      选项 2

      如果你没有,那么你可能需要使用这个:

      import json
      import pandas as pd
      
      df = pd.read_csv('data/file.csv', converters={'json_column_name': eval})
      

      选项 3

      对于更复杂的情况,您可以像这样编写自定义转换器:

      import json
      import pandas as pd
      
      def parse_column(data):
          try:
              return json.loads(data)
          except Exception as e:
              print(e)
              return None
      
      
      df = pd.read_csv('data/file.csv', converters={'json_column_name': parse_column})
      

      【讨论】:

      • 您好,我的 JSON 字符串 'sv' 中有 nan 值:[nan, nan, nan, nan, nan, 1.0] 我收到错误“名称 'nan' 未定义” .你知道如何处理这种情况吗?
      • 嗯,您可以尝试选项 3,自定义解析器并执行类似 data = data.replace('nan,', 'None,') 之类的操作,然后返回 eval(data),但要小心替换,以及您不想替换的其他值被替换。我不确定你的数据是什么样的。你可能会变得更聪明一点,并使用像 (?<=[\[,\s\]])(nan)(?=[\,\s\]]) 这样的正则表达式,它应该匹配所有的 nan 但不是像 bnan*nan 这样的东西 - 这是一个在 regexr.com 上玩耍的好工具跨度>
      【解决方案3】:

      Paul 的原始答案非常好,但总的来说并不正确,因为不能保证最后一行的左侧和右侧的列顺序相同。 (其实对问题中的测试数据似乎不起作用,而是错误地切换了身高和体重列。)

      我们可以通过确保 LHS 上的 dict 键列表已排序来解决此问题。这是因为 RHS 上的 apply 自动按索引排序,在本例中是列名列表。

      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 function in 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)
      

      【讨论】:

      • 感谢您的回答。 ujson.loads 不应该是 json.loads 吗?
      【解决方案5】:

      有一个稍微简单的方法,但最终你必须调用 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 以标准方式读取数据,但对于 stats 列使用我们的自定义解析器。这将使 stats 列成为 dict

      从这里开始,我们可以使用一个小技巧,在一个步骤中直接将这些列附加到适当的列名。这仅适用于常规数据(json 对象需要有 3 个值或至少需要在我们的 CustomParser 中处理缺失值)

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

      在左侧,我们从 stats 列元素的键中获取新列名。 stats 列中的每个元素都是一个字典。所以我们正在做一个批量分配。在右侧,我们使用 apply 分解“stats”列,从每个键/值对中创建一个数据框。

      【讨论】:

      • 谢谢,这太好了,我希望将来我需要处理更多的突变数据,这会有所帮助。
      • 此答案的最后一行不能保证 dict 元素与正确的列名匹配。 .apply(pandas.Series) 将每一行转换为一个系列并自动对索引进行排序,在本例中为字典键列表。因此,为了保持一致性,您必须确保 LHS 上的键列表已排序。
      • 我会 import json 然后使用:pandas.read_csv(f1, converters={'stats': json.loads})。你不需要定义一个新的函数,你也绝对不需要在里面导入。
      • 你好。我在 Python 3 中尝试过这个并得到了错误:ValueError: Columns must be the same length as key。我的要求和预期输出完全相同,只是我的 JSON 中有嵌套值。
      • 唯一的问题是当 json 键不一致时,列必须与键错误弹出相同的长度
      【解决方案6】:

      我认为应用 json.load 是一个好主意,但是从那里您可以直接将其转换为数据框列,而不是再次写入/加载它:

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

      【讨论】:

      • ty,这对于我目前的任务来说已经足够了,但我将另一个标记为答案,因为它更广泛地适用
      • 我想知道如何并行化这条语句 df.join(df['stats'].apply(json.loads).apply(pd.Series))。有什么帮助吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-12
      • 2020-07-01
      • 2016-02-26
      • 1970-01-01
      • 2011-08-30
      • 2018-04-13
      相关资源
      最近更新 更多