Pandas 是 python 的一个用于数据分析处理的库,用过 R 的同学看到里面的 DataFrame 会比较亲切,应该是从 R 那里有不少的借鉴。不过它立足于 python 这样一个全栈式的语言确实还是有着很大的优势。最近在看一个用 R 做机器学习的书籍,看了一些觉得 R 确实是一个让人捉急的语言,即便它看起来也足够简洁,但还是觉得像我这样不断的更换语言却没有一门精通的很是捉急(其实也没那么悲观,毕竟 js 貌似还不错,但是很多地方用不上,nodejs 毕竟接触的也少)于是决定多多使用 python。
从 mysql 的 csv 导入到 DataFrame
MySQL export csv and pandas read csv
这里讲的很明白了。
大文本的处理
当初我自以为 4G 的内存的电脑已经够了呢,可惜计划赶不上变化,最近处理的数据普遍超过 1GB 稍加处理或者是多载入记过就会出现内存不足并且疯狂占用硬盘的情况(就是虚拟内存),过不多久小 air 就告诉我你必须把 terminal 给干掉才行…即便没有报错,使用虚拟内存的话,程序的运行速度也是慢了许多的。而且我所做的数据分析处理的工作很多时候是重复的尝试,所以每次要等个几分钟积累起来也是很耗时的。
并且,不是没有可优化的空间啊!首先,python 的 open — readline 方法本来就支持按行读取数据,然后惊喜的发现 pandas 的 read_csv
有一个参数 chunksize
就是说要一次读取多少行数据,然后可以通过一个迭代来处理数据了。
df = pd.read_csv('test.csv', chunksize=10000)
for chunk in df:
# TODO: process the chunk as a normal DataFrame
大批量读入的问题解决了,如果我要将输入的数据处理成另一个大文本要怎么做呢?这岂不是还是要在内存中搞出来一个超占空间的 DataFrame 么?pandas 的 DataFrame.to_csv
也考虑这个问题了。我们只需要添加一个参数 mode='a'
让对文件的写入是 append 模式即可。不过要注意这样写入的时候需要设置 header=None
避免多次写入表头。
多多使用 apply & map 而不是循环
DataFrame 支持一个按照行做迭代的方法,大体使用方式如下:
for idx, row in dataframe.iterrows():
# TODO: handle index and row
但是遗憾的是这里的 row 并不是引用,而是一个拷贝,因此对于在 row 上面的修改是不会表现在原始的 DataFrame 上面的。而且 pandas 其实并不鼓励这样的做法。pandas 更希望让大家使用它的 apply 以及 map 方法。
def hanlder(x):
# TODO: handle the item and you must return a new result
newdf = df.apply(hanlder, axis=1)
# or...
df['NEWROW'] = df['ONEROW'].map(hanlder)
apply 以及 map 都是会建立新的 DataFrame 因此要做赋值。然后这里还要说明的就是 apply 这样的方法(还有很多其他的方法)都会需要提供一个 axis 的参数,axis 是轴线或者是坐标轴的意思,它决定我们的函数是按照行处理的还是按照列处理的。
看一下 DataFrame.sum 的 help,看以看到:
axis : {0, 1} 0 for row-wise, 1 for column-wise
axis=0 其结果就是将列值一致的数据加在了一起,而 axis=1 就是将行值一致的数据加在了一起。那么可以解释为 row-wise 就是滑动 row,固定其他,而 column-wise 就是行固定,滑动其他。
再看一下 DataFrame.apply 的 help:
axis : {0, 1} 0 : apply function to each column 1 : apply function to each row
其结果是一直的,但是注释却很不相同…整体来说我觉得 row-wise column-wise 很是让人迷惑,不如就认为 0 是按照列聚合,1是按照行聚合。这似乎和我们通常认为的行在前列在后是相反的…不过就是这样子了…
DataFrame 的 join & concat
之前用 mysql 做各种 join 自以为很方便,但是用了 pandas 之后感觉这样似乎也很流畅,并且文本之间的交换什么的也更方便一些。
pandas 里面的 join 就是 merge,下面给一个示例吧,具体就不说了。
newdf = df1.merge(df2, left_on='KEY1', right_on='KEY2', how='left')
然后有一个小技巧,理论上 Series 是不能和 DataFrame merge 的,但是…Series 可以很容易的转换成 DataFrame。
df = DataFrame(series)
concat 则是做按照行的合并。通常的情景是我们获取了多个 DataFrame 然后把它们放进一个数组中,然后调用 pd.concat(dflist)
即可。
groupby 中的自定义 agg
最近在处理一批电商的数据,我需要将同一个会话的用户日志按照时间线合并到一个字段之中。那么大体的代码如下:
result = grouped.agg({'urltype': lambda x: ' '.join(x),
'seller': lambda x: ' '.join(map(str, x[x.notnull()])),
'cid': lambda x: ' '.join(map(lambda x: str(int(x)), x[x.notnull()])),
'ciddesc': lambda x: ' '.join(x[x.notnull()]),
'uid': 'max'})
一些相关的内容可见 http://stackoverflow.com/questions/14529838/apply-multiple-functions-to-multiple-groupby-columns
Time Series
看了那本 Python for Data Analysis 的有关时间序列的章节的前半部分。主要是将在 pandas 中使用时间序列做索引的很多好处以及 pandas 对于生成各种诡异的 date_range 的支持都相当的强大。这次使用的非常有限,主要是把 log 的时间设置成索引的主要目的是为了方便的抽取一天的数据:
df = df['2013-05-03'] # one day
df = df['2013-05'] # one month
时间序列索引直接可以用字符串来获取,非常的方便。
一点小感想
- Pandas 用起来还是不错的,而且借助 python 的平台,还可以和其他很多库一起使用,能做很多事情。
- iPython 确实很好用,但是…还是.py文件靠谱啊…一不小心就记不得之前做的操作了,然后就又要翻工了…反而是把一步步的处理记录下来更靠谱一些。
- 遇到大量的数据应该首先抽样一部分数据快速的验证自己的程序是不是靠谱,而不是一口气将所有的数据都载入来测试,这样会让整个过程慢很多很多。
- Python 的 lambda 表达式真是有点鸡肋,不如做成 ruby 或者 javascript 那样的匿名函数呀(ruby 那个叫 block 来着),允许多行就那么难么。到目前为止用的最爽的语言还是 javascript。
- 这种实验性的脚本会生成大量的中间文件(各种半成品)相当会乱,而且代码的维护也很混乱,我已经很久没有提交 commit 了,需要想个好办法呀…