Python 使用Pandas解析从CSV加载的JSON字符串

Python 使用Pandas解析从CSV加载的JSON字符串,python,pandas,Python,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/19

我正在处理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))
这似乎是我做错了,考虑到我需要定期在三个专栏上做这件事,这是一项相当大的工作

所需的输出是下面的dataframe对象。添加了以下代码行,以我的(蹩脚的)方式达到目的:


我认为应用
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))

有一种稍微简单一点的方法,但最终您必须调用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中处理缺少的值)


在左侧,我们从stats列元素的键中获取新列名。stats列中的每个元素都是一个字典。因此,我们正在进行批量分配。在右侧,我们使用apply分解“stats”列,以从每个键/值对中生成一个数据帧。

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)
软件包有助于做到这一点,而无需使用自定义函数

(假设您正在从文件加载数据)


选项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})

ty,这对于我当前的任务来说已经足够了,但我将另一个标记为答案,因为它的应用范围更广。我想知道如何将这个语句df.join(df['stats'].apply(json.loads.apply)(pd.Series))并行化。有什么帮助吗?谢谢,这很好,我希望我将来需要处理更多的变异数据,这会有所帮助。这个答案的最后一行不能保证dict元素与正确的列名匹配
.apply(pandas.Series)
将每一行转换为一个序列,并自动对索引进行排序,在本例中,索引是字典键的列表。因此,为了保持一致性,您必须确保LHS上的键列表已排序。我将
导入json
,然后使用:
pandas.read_csv(f1,converters={'stats':json.loads})
。您不需要定义一个新函数,也不需要在其中导入。您好。我在Python3中尝试了这一点,得到了错误:ValueError:列的长度必须与键的长度相同。我的需求和预期的输出完全相同,只是我在JSON中有嵌套的值。唯一的问题是当JSON键不一致时,列的长度必须与键错误popsThx的长度相同,才能发现这一点。我已经更新了我的答案和你的额外排序为完整谢谢你的回答。
ujson.loads
不应该是
json.loads
?您好,我在我的json sting'sv'中得到了nan值:[nan,nan,nan,nan,nan,1.0],我得到了错误“名称'nan'未定义”。您知道如何处理这种情况吗?嗯,您可以尝试选项3,自定义解析器,执行类似data=data.replace('nan','None',)的操作,然后返回eval(data),但要小心替换,以及其他您不想替换的值。我不确定你的数据是什么样子的。您可能会变得更聪明一点,并使用类似这样的正则表达式
(?)?
import json
import pandas as pd

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

df = pd.read_csv('data/file.csv', converters={'json_column_name': eval})
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})