Pandas DataFrame中存储为字符串的列表:如何转换回列表
我有一个 n 行 m 列的 Pandas 数据框 df
,定义如下。(我知道这不是最好的做法,但对我实际代码来说是有意义的,不过这在这里说太多了,所以就相信我,这种方法在我的特定场景下是有效的。)
>>> df = DataFrame(columns=['col1'])
>>> df.append(Series([None]), ignore_index=True)
>>> df
Empty DataFrame
Columns: [col1]
Index: []
我在这个数据框的单元格里存储了列表,方法如下。
>>> df['column1'][0] = [1.23, 2.34]
>>> df
col1
0 [1, 2]
但不知道为什么,这个数据框把这个列表存成了字符串,而不是列表。
>>> df['column1'][0]
'[1.23, 2.34]'
我有两个问题想问你。
- 为什么数据框会把列表存成字符串?有没有办法解决这个问题?
- 如果没有,那有没有一种 Python 的方式可以把这个字符串转换成列表?
更新
我使用的数据框是从 CSV 格式保存和加载的。是这个格式,而不是数据框本身,把列表从字符串转换成了字面量。
9 个回答
1) 有办法解决这个问题。这里可以用 loc 来帮助你。
>>> import pandas as pd
>>> df = pd.DataFrame(columns=['column1'])
>>> df = df.append(pd.Series(data = {'column1':[None]}), ignore_index = True)
column1
0 [None]
>>> # Add list to index 0 in column1
>>> df.loc[0,'column1'] = [1.23, 2.34]
>>> print(df.loc[0, 'column1'])
[1.23, 2.34]
2) 用 Python 的方式把这个字符串转换成一个列表。(这可能正是你想要的,因为你使用的数据框是从 CSV 格式保存和加载的,这里有几种解决方案)。这是对 pshep123 回答的补充。
from ast import literal_eval
import pandas as pd
csv = io.StringIO(u'''
id list
A1 [1,2]
A2 [3,4]
A3 [5,6]
''')
df = pd.read_csv(csv, delim_whitespace = True)
# Output is a string
df.loc[0, 'list']
'[1,2]'
# Convert entire column to a list
df.loc[:,'list'] = df.loc[:,'list'].apply(lambda x: literal_eval(x))
# Output is a list
df.loc[0, 'list']
[1, 2]
我刚遇到这个问题,发现有一个非常简单的解决办法(pandas.eval())。我使用的是pandas 0.20.0版本。
# SETUP
import pandas as pd
import io
csv = io.StringIO(u'''
id list
A1 [1,2]
A2 [3,4]
A3 [5,6]
''')
df = pd.read_csv(csv, delim_whitespace = True)
# TYPE CHECK <type 'str'>
print type(df.at[0, 'list'])
# MAIN CONVERSION
df['list'] = pd.eval(df['list'])
# TYPE CHECK <type 'list'>
print type(df.at[0, 'list'])
- 使用
ast.literal_eval
来安全地评估一个包含 Python 字面量或 容器数据类型 的字符串。这是 Python 标准库的一部分。
使用 python 的 eval() 和 ast.literal_eval() 的区别 解释了为什么
literal_eval
比使用eval
更安全。示例:
literal_eval("[1.23, 2.34]")
可以正常工作。literal_eval("['KB4523205','KB4519569','KB4503308']")
也可以正常工作。- 其他答案提到
pd.eval
,但它的使用有限;在这个简单的例子中会出现ValueError: NumExpr 2 does not support Unicode as a dtype.
的错误。
- 其他答案提到
literal_eval("[KB4523205, KB4519569, KB4503308]")
不可以工作(因为str
值没有加引号)。- 可以查看 pandas - 将字符串转换为字符串列表 来处理这种表示方式。
- 在读取文件时,可以通过使用
pandas.read_csv
的converters
参数来转换列。
在 test.csv
中的数据
col1
"[1.23, 2.34]"
"['KB4523205','KB4519569','KB4503308']"
在创建 csv 时转换列
from ast import literal_eval
import pandas as pd
# convert the column during import
df = pd.read_csv('test.csv', converters={'col1': literal_eval})
# display(df)
col1
0 [1.23, 2.34]
1 [KB4523205, KB4519569, KB4503308]
# check type
print(type(df.iloc[0, 0]))
list
print(type(df.iloc[1, 0]))
list
转换现有数据框的列
df.col1 = df.col1.apply(literal_eval)
%%timeit
pd.eval
的速度比literal_eval
慢 28 倍。- 假设
test.csv
有 2,820,511 行数据,每行是"[1.23, 2.34]"
。
你可以直接使用 pandas -
import pandas as pd
df = pd.read_csv(DF_NAME, converters={'COLUMN_NAME': pd.eval})
这样做会把那一列的数据按照它在 Python 中对应的数据类型读取,而不是当成字符串。
更新:
正如 @ctwardy 在评论中指出的,使用 pd.eval
比使用 eval
更明智,这样可以避免一些意外的正则表达式相关的问题。详细信息可以查看这个链接 - https://realpython.com/python-eval-function/#minimizing-the-security-issues-of-eval
正如你所提到的,这种情况在保存和加载 pandas 的数据框(DataFrame)为 .csv
文件时很常见,因为 .csv
是一种文本格式。
在你的例子中,这种情况发生是因为列表对象有字符串表示,这样它们就可以被存储为 .csv
文件。加载这个 .csv
文件时,就会得到那个字符串表示。
如果你想保存实际的对象,应该使用 DataFrame.to_pickle()
(注意:对象必须是可序列化的!)。
至于你的第二个问题,你可以使用 ast.literal_eval
将其转换回来:
>>> from ast import literal_eval
>>> literal_eval('[1.23, 2.34]')
[1.23, 2.34]