标签:
近期的工作和Hive SQL打交道比较多,偶尔遇到一些SQL不好解决的问题,会将文件下载下来用pandas来处理,由于数据量比较大,因此有一些相关的经验可以和大家分享,希望对大家学习pandas有所帮助吧。
大文本数据的读写
有时候我们会拿到一些很大的文本文件,完整读入内存,读入的过程会很慢,甚至可能无法读入内存,或者可以读入内存,但是没法进行进一步的计算,这个时候如果我们不是要进行很复杂的运算,可以使用read_csv提供的chunksize或者iterator参数,来部分读入文件,处理完之后再通过to_csv的mode=’a’,将每部分结果逐步写入文件。
import pandas as pd
input = pd.read_csv(’input.csv’, chunksize=1000000)
for i in input:
chunk = dosomethig(input)# 进行一些操作
chunk.to_csv(’output.csv’, mode=’a’, header=False) # 记得使用header=False不然会重复写入列名
input = pd.read_csv(’input.csv’, iterator=True)
while loop:
try:
chunk = reader.get_chunk(1000000)
chunk.to_csv(’output.csv’, mode=’a’, header=False) # 和上面的代码作用一样只是通过iterator来实现
except StopIteration:
break
to_csv, to_excel的选择
在输出结果时统称会遇到输出格式的选择,平时大家用的最多的.csv, .xls, .xlsx,后两者一个是excel2003,一个是excel2007,我的经验是csv>xls>xlsx,大文件输出csv比输出excel要快的多,xls只支持60000+条记录,xlsx虽然支持记录变多了,但是,如果内容有中文常常会出现诡异的内容丢失。因此,如果数量较小可以选择xls,而数量较大则建议输出到csv,xlsx还是有数量限制,而且大数据量的话,会让你觉得python都死掉了
读入时处理日期列
我之前都是在数据读入后通过to_datetime函数再去处理日期列,如果数据量较大这又是一个浪费时间的过程,其实在读入数据时,可以通过parse_dates参数来直接指定解析为日期的列。它有几种参数,TRUE的时候会将index解析为日期格式,将列名作为list传入则将每一个列都解析为日期格式
关于to_datetime函数再多说几句,我们拿到的时期格式常常出现一些乱七八糟的怪数据,遇到这些数据to_datimetime函数默认会报错,其实,这些数据是可以忽略的,只需要在函数中将errors参数设置为’ignore’就可以了。
另外,to_datetime就像函数名字显示的,返回的是一个时间戳,有时我们只需要日期部分,我们可以在日期列上做这个修改,datetime_col = datetime_col.apply(lambda x: x.date()),用map函数也是一样的datetime_col = datetime_col.map(lambda x: x.date())
把一些数值编码转化为文字
前面提到了map方法,我就又想到了一个小技巧,我们拿到的一些数据往往是通过数字编码的,比如我们有gender这一列,其中0代表男,1代表女。当然我们可以用索引的方式来完成
data[’gender’].ix[data[’gender’]==0] = u’女’data[’gender’].ix[data[’gender’]==1] = u’男’#这里要注意一下,上面的写法是给索引找到的列修改值,而下面的方法则不会对原有值进行修改data.ix[data[’gender’]==0][’gender’] = u’女’data.ix[data[’gender’]==1][’gender’] = u’男’
其实我们有更简单的方法,对要修改的列传入一个dict,就会达到同样的效果。
data[’gender’] = data[’gender’].map({0:’男’, 1:’女’})
通过shift函数求用户的相邻两次登录记录的时间差
之前有个项目需要计算用户相邻两次登录记录的时间差,咋看起来其实这个需求很简单,但是数据量大起来的话,就不是一个简单的任务,拆解开来做的话,需要两个步骤,第一步将登录数据按照用户分组,再计算每个用户两次登录之间的时间间隔。数据的格式很单纯,如下所示
uid time111 2016-05-01112 2016-05-02111 2016-05-03113 2016-05-04113 2016-05-05112 2016-05-06
如果数据量不大的,可以先unique uid,再每次计算一个用户的两次登录间隔,类似这样
reg_data = reg_data.sort_values([’uid’, time]) # 先按照uid和time排一下序uids = reg_data[’uid’].unique() # 获得所有的uidfor u in uid:
data = []
uid_reg_data = reg_data.ix[reg_data[’uid’]]
pre = None
for i, row in uid_reg_data.iterrows():
if len(pre) = 0:
pre = row[’time’]
continue
row[’days’] = (row[’time’] - pre).days
data.append(row)
pre = row[’time’]
reg_data_f = pd.DataFrame(pre)
reg_data_f.to_csv(’output.csv’, mode=’a’, header=False)
这种方法虽然计算逻辑比较清晰易懂,但是缺点也非常明显,计算量巨大,相当与有多少量记录就要计算多少次。
那么为什么说pandas的shift函数适合这个计算呢?来看一下shift函数的作用
col1
AAAA
BBBB
CCCC
DDDD
假设我们有上面这样是数据,如果我们使用cols.shift(1),会得到下面的结果
col1
NaN
AAAA
BBBB
CCCC
刚好把值向下错位了一位,是不是恰好是我们需要的。让我们用shift函数来改造一下上面的代码。
reg_data = reg_data.sort_values([’uid’, time]) # 先按照uid和time排一下序uids = reg_data[’uid’].unique() # 获得所有的uidfor u in uid:
data = []
uid_reg_data = reg_data.ix[reg_data[’uid’]]
uid_reg_data[’pre’] = uid_reg_data[’time’].shift(1)
uid_reg_data[’days’] = (uid_reg_data[’time’] - uid_reg_data[’pre’]).map(lambda x:x.days)
uid_reg_data.ix[~uid_reg_data[’pre’].isnull()].to_csv(’output.csv’, mode=’a’, header=False)
计算量一下就减少了几个数量级。不过在我的实际应用场景中还是远远不够,我碰到登录日志是10亿级,用户数是千万级的。有没有更简单的方法,答案是有的,有一个小技巧。先上代码。
reg_data = reg_data.sort_values([’uid’, time]) # 先按照uid和time排一下序reg_data[’pre’] = reg_data[’time’].shift(1)reg_data[’uid0’] = reg_data[’uid0’].shift(1)reg_data[’days’] = (reg_data[’time’] - reg_data[’pre’]).map(lambda x:x.days)reg_data_f = reg_data.ix(reg_data[’uid’] == reg_data[’uid0’]
上面的代码就把pandas向量化计算的优势发挥出来了,规避掉了计算过程中最耗费时间的按uid循环。如果我们的uid都是一个只要排序后用shift(1)就可以取到所有前一次登录的时间,不过真实的登录数据中有很多的不用的uid,因此再将uid也shift一下命名为uid0,保留uid和uid0匹配的记录就可以了。
来源:Cloga的互联网笔记
标签: