在Python Pandas中对千万元素的分块文件进行groupby时遇到问题
我有一个非常大的CSV文件(几十个GB),里面包含了网页日志,列有:user_id
(用户ID)、time_stamp
(时间戳)、category_clicked
(点击的类别)。我需要建立一个评分系统,来识别用户喜欢和不喜欢的类别。需要注意的是,我有超过1000万的用户。
我首先把文件分成小块,存储在一个叫input.h5
的HDFStore
中,然后我使用groupby
根据user_id
进行分组,按照Jeff的方法。
我的数据大约有2亿行,1000万个独特的用户ID。
user id | timestamp | category_clicked
20140512081646222000004-927168801|20140722|7
20140512081714121000004-383009763|20140727|4
201405011348508050000041009490586|20140728|1
20140512081646222000004-927168801|20140724|1
20140501135024818000004-1623130763|20140728|3
这是我的pandas版本:
INSTALLED VERSIONS
------------------
commit: None
python: 2.7.6.final.0
python-bits: 64
OS: Windows
OS-release: 8
machine: AMD64
processor: AMD64 Family 21 Model 2 Stepping 0, AuthenticAMD
byteorder: little
LC_ALL: None
LANG: fr_FR
pandas: 0.13.1
Cython: 0.20.1
numpy: 1.8.1
scipy: 0.13.3
statsmodels: 0.5.0
IPython: 2.0.0
sphinx: 1.2.2
patsy: 0.2.1
scikits.timeseries: None
dateutil: 2.2
pytz: 2013.9
bottleneck: None
tables: 3.1.1
numexpr: 2.3.1
matplotlib: 1.3.1
openpyxl: None
xlrd: 0.9.3
xlwt: 0.7.5
xlsxwriter: None
sqlalchemy: 0.9.4
lxml: None
bs4: None
html5lib: None
bq: None
apiclient: None
我想要的输出是这样的:
对于每个用户ID,生成一个列表[0.1,0.45,0.89,1.45,5.12,0.,0.,0.45,0.12,2.36,7.8]
,这个列表代表用户在每个类别上的得分,还有一个总得分。我不能告诉你更多关于得分的细节,但计算得分需要所有的时间戳和点击的类别。你不能事后再去加总或做其他处理。
这是我的代码:
clean_input_reader = read_csv(work_path + '/input/input.csv', chunksize=500000)
with get_store(work_path+'/input/input.h5') as store:
for chunk in clean_input_reader:
store.append('clean_input', chunk,
data_columns=['user_id','timestamp','category_clicked'],
min_itemsize=15)
groups = store.select_column('clean_input','user_id').unique()
for user in groups:
group_user = store.select('clean_input',where=['user_id==%s' %user])
<<<<TREATMENT returns a list user_cat_score>>>>
store.append(user, Series(user_cat_score))
我的问题是:我觉得这一行代码:
group_user=store.select('clean_input',where=['user_id==%s' %user])
在时间复杂度上太重,因为我有很多组,我相信在执行store.select
时会有很多冗余的排序,如果我执行1000万次的话。
给你一个估算,我用这种方法处理1000个键需要250秒,而如果用普通的groupby
,直接读取完整的内存CSV文件,使用read_csv
而不分块,只需要1秒。
**********更新***********
在应用了Jeff的哈希方法后,我可以在1秒内处理1000个键(和完整内存方法的时间一样),并且大大减少了内存使用。唯一的时间开销是我之前没有考虑到的,就是分块、保存100个哈希组,以及从哈希组中获取真实组的时间。但这个操作不会超过几分钟。
1 个回答
这里有一个解决方案,可以让这个问题的规模变得更灵活。实际上,这就像是这个问题的高密度版本,具体可以参考这里
首先,定义一个函数,把特定的组值转换成更少的组。我的设计思路是把你的数据集分成可以在内存中轻松处理的小块。
def sub_group_hash(x):
# x is a dataframe with the 'user id' field given above
# return the last 2 characters of the input
# if these are number like, then you will be sub-grouping into 100 sub-groups
return x['user id'].str[-2:]
使用上面提供的数据,这样就可以在输入数据上创建一个分组框架:
In [199]: [ (grp, grouped) for grp, grouped in df.groupby(sub_group_hash) ][0][1]
Out[199]:
user id timestamp category
0 20140512081646222000004-927168801 20140722 7
3 20140512081646222000004-927168801 20140724 1
其中,grp
是组的名称,grouped
是结果框架。
# read in the input in a chunked way
clean_input_reader = read_csv('input.csv', chunksize=500000)
with get_store('output.h5') as store:
for chunk in clean_input_reader:
# create a grouper for each chunk using the sub_group_hash
g = chunk.groupby(sub_group_hash)
# append each of the subgroups to a separate group in the resulting hdf file
# this will be a loop around the sub_groups (100 max in this case)
for grp, grouped in g:
store.append('group_%s' % grp, grouped,
data_columns=['user_id','timestamp','category_clicked'],
min_itemsize=15)
现在你有一个包含100个子组的hdf文件(如果不是所有组都有数据,可能会更少),每个子组都包含进行操作所需的所有数据。
with get_store('output.h5') as store:
# all of the groups are now the keys of the store
for grp in store.keys():
# this is a complete group that will fit in memory
grouped = store.select(grp)
# perform the operation on grouped and write the new output
grouped.groupby(......).apply(your_cool_function)
这样一来,这个问题的复杂度就减少了100倍。如果这样还不够,那就简单地增加sub_group_hash,来创建更多的组。
你应该尽量让组的数量保持较小,因为HDF5在处理较少的组时效果更好(比如不要创建1000万个子组,这样就失去了意义,100、1000甚至1万都是可以的)。不过我觉得100个应该对你有帮助,除非你的组密度非常高(比如某个组的数据量巨大,而其他组的数据量很少)。
注意,这个问题的规模扩展起来很简单;如果需要,你可以把子组存储在不同的文件中,或者单独处理它们(并行处理)。
这样一来,你的解决方案的时间复杂度大约是O(子组数量)
。