pandas read_csv
函数似乎没有稀疏选项。我有csv数据,其中有大量零(它压缩得非常好,并且剥离任何0
值会将其减少到几乎原始大小的一半)。
我尝试先用read_csv
将其加载到密集矩阵中,然后调用to_sparse
,但是文本字段需要很长时间和扼流圈,尽管大多数数据都是浮点数。如果我先调用pandas.get_dummies(df)
将分类列转换为1&零,然后调用to_sparse(fill_value=0)
它需要一个荒谬的时间,比我期望的大多数数字表有更长的时间,大多数为零。即使我从原始文件中删除零并调用to_sparse()
(以便填充值为NaN),也会发生这种情况。无论我是通过kind='block'
还是kind='integer'
,都会发生这种情况。
除了手工构建稀疏数据帧之外,是否有一种很好的,平滑的方式可以直接加载稀疏的csv,而不会占用大量不必要的内存?
以下是一些代码,用于创建包含3列浮点数据和一列文本数据的样本数据集。大约85%的浮点值为零,CSV的总大小约为300 MB,但您可能希望将其放大以真正测试内存约束。
np.random.seed(123)
df=pd.DataFrame( np.random.randn(10000000,3) , columns=list('xyz') )
df[ df < 1.0 ] = 0.0
df['txt'] = np.random.choice( list('abcdefghij'), size=len(df) )
df.to_csv('test.csv',index=False)
这是一种阅读它的简单方法,但希望有一种更好,更有效的方法:
sdf = pd.read_csv( 'test.csv', dtype={'txt':'category'} ).to_sparse(fill_value=0.0)
编辑添加(来自JohnE):如果可能,请在答案中提供有关读取大型CSV的一些相对性能统计信息,包括有关如何衡量内存效率的信息(特别是内存效率更难)测量比时钟时间)。特别要注意的是,较慢的(时钟时间)答案可能是最佳答案,如果内存效率更高。
答案 0 :(得分:15)
我可能会通过使用dask以流式方式加载您的数据来解决这个问题。例如,您可以按如下方式创建一个dask数据框:
import dask.dataframe as ddf
data = ddf.read_csv('test.csv')
此data
对象此时并未实际执行任何操作;它只包含一个&#34;配方&#34;在可管理的块中从磁盘读取数据帧的排序。如果要实现数据,可以致电compute()
:
df = data.compute().reset_index(drop=True)
此时,您有一个标准的pandas数据帧(我们称之为reset_index
,因为默认情况下每个分区都是独立索引的)。结果等同于直接调用pd.read_csv
得到的结果:
df.equals(pd.read_csv('test.csv'))
# True
dask的好处是你可以为这个&#34;配方添加指令&#34;用于构建数据框;例如,您可以按如下方式使数据的每个分区稀疏:
data = data.map_partitions(lambda part: part.to_sparse(fill_value=0))
此时,调用compute()
将构造一个稀疏数组:
df = data.compute().reset_index(drop=True)
type(df)
# pandas.core.sparse.frame.SparseDataFrame
要检查dask方法与原始熊猫方法的比较,让我们进行一些线性分析。我会按here所述使用lprun
和mprun
(完全披露:这是我自己的一部分内容)。
假设您正在使用Jupyter笔记本,您可以这样运行:
首先,使用我们想要执行的基本任务创建一个单独的文件:
%%file dask_load.py
import numpy as np
import pandas as pd
import dask.dataframe as ddf
def compare_loads():
df = pd.read_csv('test.csv')
df_sparse = df.to_sparse(fill_value=0)
df_dask = ddf.read_csv('test.csv', blocksize=10E6)
df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
df_dask = df_dask.compute().reset_index(drop=True)
接下来让我们对计算时间进行逐行分析:
%load_ext line_profiler
from dask_load import compare_loads
%lprun -f compare_loads compare_loads()
我得到以下结果:
Timer unit: 1e-06 s
Total time: 13.9061 s
File: /Users/jakevdp/dask_load.py
Function: compare_loads at line 6
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 def compare_loads():
7 1 4746788 4746788.0 34.1 df = pd.read_csv('test.csv')
8 1 769303 769303.0 5.5 df_sparse = df.to_sparse(fill_value=0)
9
10 1 33992 33992.0 0.2 df_dask = ddf.read_csv('test.csv', blocksize=10E6)
11 1 7848 7848.0 0.1 df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
12 1 8348217 8348217.0 60.0 df_dask = df_dask.compute().reset_index(drop=True)
我们看到大约60%的时间花在了dask调用上,而大约40%的时间花在pandas调用上面的示例数组中。这告诉我们dask比这个任务慢大约50%:这是可以预料到的,因为数据分区的分块和重组会导致一些额外的开销。
dask闪耀在内存使用中:让我们使用mprun
来执行逐行内存配置文件:
%load_ext memory_profiler
%mprun -f compare_loads compare_loads()
我机器上的结果是:
Filename: /Users/jakevdp/dask_load.py
Line # Mem usage Increment Line Contents
================================================
6 70.9 MiB 70.9 MiB def compare_loads():
7 691.5 MiB 620.6 MiB df = pd.read_csv('test.csv')
8 828.8 MiB 137.3 MiB df_sparse = df.to_sparse(fill_value=0)
9
10 806.3 MiB -22.5 MiB df_dask = ddf.read_csv('test.csv', blocksize=10E6)
11 806.4 MiB 0.1 MiB df_dask = df_dask.map_partitions(lambda part: part.to_sparse(fill_value=0))
12 947.9 MiB 141.5 MiB df_dask = df_dask.compute().reset_index(drop=True)
我们看到最终的pandas数据帧大小约为140MB,但是pandas在将数据读入临时密集对象的过程中使用了大约620MB。
另一方面,dask在加载数组和构造最终稀疏结果时仅使用~140MB。如果您正在读取其密集大小与系统中可用内存相当的数据,则dask具有明显的优势,尽管计算时间缩短了约50%。
但是对于处理大数据,你不应该停在这里。据推测,您正在对数据进行一些操作,并且dask数据帧抽象允许您在实现数据之前执行这些操作(即将它们添加到&#34; recipe&#34;)。因此,如果您对数据的处理涉及算术,聚合,分组等,您甚至不必担心稀疏存储:只需使用dask对象执行这些操作,调用{{1}最后,dask将以内存有效的方式处理它们。
因此,例如,我可以使用dask数据帧计算每列的compute()
,而无需一次将整个内容加载到内存中:
max()
直接使用dask数据帧可以避免对数据表示的担忧,因为您可能永远不必一次将所有数据加载到内存中。
祝你好运!
答案 1 :(得分:9)
这里提供的答案主要是作为基准。希望有比这更好的方法。
chunksize = 1000000 # perhaps try some different values here?
chunks = pd.read_csv( 'test.csv', chunksize=chunksize, dtype={'txt':'category'} )
sdf = pd.concat( [ chunk.to_sparse(fill_value=0.0) for chunk in chunks ] )
正如@acushner所说,您可以将其作为生成器表达式来执行:
sdf = pd.concat( chunk.to_sparse(fill_value=0.0) for chunk in chunks )
似乎已经达成共识,这比列表补偿方式更好,尽管在我的测试中我没有看到任何大的差异,但也许你可能有不同的数据。
我希望能报告一些关于各种方法的内存分析,但我很难得到一致的结果,我怀疑是因为python总是在后台清理内存,导致一些随机噪声被添加到结果中。 (在对Jake的答案的评论中,他建议在每个%memit
之前重新启动jupyter内核以获得更一致的结果,但我还没有尝试过。)
但我确实一直发现(使用%%memit
)上面的分块和@jakevdp的dask方法都使用了一些非常粗略的内存附近的东西作为OP中的朴素方法。有关分析的更多信息,您应该查看&#34;分析和时序代码&#34;在杰克的书和#34; Python数据科学手册&#34;。