扩展pandas字符串列的浮动内存有效

时间:2018-01-26 09:34:04

标签: python performance pandas dataframe

我有一个像这样的DataFrame:

df = pd.DataFrame([['Col1Val', 'Col2Val', '[3, 31.1, -341.4, 54.13]']],
                  columns=['col1', 'col2','values'])

唯一的区别是我有几百万行,而values列是每行中200个浮点数的字符串,而不是我的示例中的4个。

包含此数据的csv文件大约为5 GB。但是,在将前2个字符串列转换为类别后加载到pandas中时,这会减少。因此,我能够执行大多数操作(过滤,切片,索引)而没有性能问题。

我需要将values字符串列扩展为单独的浮点列。因此将有200列,每列包含一个浮点数。我试图执行此操作,但我一直没有记忆。从理论上讲,我认为这应该是以内存有效的方式逐行进行的,因为浮点数列应该比字符串中的许多数字占用更少的内存。什么是一个很好的算法呢?

我的现有代码如下所示,用于拆分values列。

df['values'] = df['values'].str.replace('[','').str.replace(']','')

# code runs out of memory in next line!
df_values = pd.DataFrame([x.split(',') for x in df['values'].values.tolist()])

df_values[df_values.columns] = df_values[df_values.columns].apply(pd.to_numeric, errors='coerce')
df_values[df_values.columns] = df_values[df_values.columns].fillna(0.0)

df= df.drop('values', 1).join(df_values)

我的示例的预期结果,上面的代码为少量行正确生成:

df = pd.DataFrame([['Col1Val', 'Col2Val', 3.0, 31.1, -341.4, 54.13]],
                  columns=['col1', 'col2', 0, 1, 2, 3])

为了理解为什么我希望(希望!)因为记忆减少而推理我的理由。解决方案,floats通常应占用的空间小于string

from sys import getsizeof

getsizeof('334.34')      #55
getsizeof(334.34)        #24
getsizeof('-452.35614')  #59
getsizeof(-452.35614)    #24

3 个答案:

答案 0 :(得分:3)

选项1
使用ast.literal_eval / pd.eval解析字符串列(这是最简单的第一步)。

import ast
df['values'] = df['values'].apply(ast.literal_eval)

接下来,展平最后一列,并concat使用剩余的n - 1列进行展示。

i = df.iloc[:, :-1]
j = pd.DataFrame(df.iloc[:, -1].tolist())

pd.concat([i, j], 1)

     col1     col2  0     1      2      3
0  Col1Val  Col2Val  3  31.1 -341.4  54.13

这是效率的改进版本。使用del进行内部删除列,并删除所有切片操作(它们创建副本,并且浪费)。

j = pd.DataFrame(df['values'].tolist())
del df['values']

pd.concat([df, j], 1)

      col1     col2  0     1      2      3
0  Col1Val  Col2Val  3  31.1 -341.4  54.13

选项2
str.extractall(不能保证表现)。

df = df.set_index(['col1', 'col2'])['values']\
       .str.extractall('(\d+(?:\.\d*)?)')\
       .unstack()

df.columns = df.columns.droplevel(0)
df.reset_index()

match     col1     col2  0     1      2      3
0      Col1Val  Col2Val  3  31.1  341.4  54.13

答案 1 :(得分:1)

您可以将pop用于apply的提取列,以转换为listDataFrame构造函数:

df1 = df.join(pd.DataFrame(df.pop('values').apply(pd.io.json.loads).values.tolist()))
print (df1)

      col1     col2  0     1      2      3
0  Col1Val  Col2Val  3  31.1 -341.4  54.13

print (df1.dtypes)
col1     object
col2     object
0         int64
1       float64
2       float64
3       float64
dtype: object

答案 2 :(得分:1)

对于较小的数据集:(如果由于内存问题导致此方法失败,请参见下文。)

您也可以试试这个。

df['values'].str[1:-1].str.split(",", expand=True).astype(float)

第一个str[1:-1]操作会删除括号。

str.split会将其余值分割为,并将其展开为数据框(使用expand=True

    0       1       2       3
0   3.0     31.1    -341.4  54.13

您还可以按[ , ]

拆分字符串
df['values'].str.split(r"[\[,\]]", expand=True).astype(float)

但这会产生两个额外的列

    0   1   2       3       4       5
0       3   31.1    -341.4  54.13   

编辑:(对于大型数据集。)

有人可能会尝试从阅读数据部分修复它。

df = pd.read_csv('test.csv', delimiter=',', quotechar='"')

在这里,我们将引号char更改为",以便忽略原始引号char '。然后我们按,拆分。然后,我们需要做一些数据预处理来修复错误的部分。

鉴于我的test.csv正在

 c1,c2,v1,v2,v3,v4
'Col1Val', 'Col2Val', '[3, 31.1, -341.4, 54.13]'
'Col1Val', 'Col2Val', '[3, 31.1, -341.4, 54.13]'
'Col1Val', 'Col2Val', '[3, 31.1, -341.4, 54.13]'

read_csv的结果是

    c1          c2          v1      v2      v3      v4
0   'Col1Val'   'Col2Val'   '[3     31.1    -341.4  54.13]'
1   'Col1Val'   'Col2Val'   '[3     31.1    -341.4  54.13]'
2   'Col1Val'   'Col2Val'   '[3     31.1    -341.4  54.13]'

现在,我们可以使用一些str方法来修复每一列。 注意:如果c1 / c2中有逗号,则结果会出错。