最近,我用pandas处理了一把大数据……
导读
pandas是python数据分析的不二选择,堪称瑞士军刀般的存在,几乎可以胜任数据分析的全过程。如果说有什么缺点的话,那么就是其不支持分布式,所以对于小数据量完全不压力,但面对大数据时却当真有些乏力。近日,自己便用pandas处理了一些大数据场景,现分享几个心得技巧。
首先简单介绍下场景:数据是每个月一份的csv文件,字段数目10个左右,单个文件记录数约6-8亿之间,单个文件体积50G+的样子。表中是一条条的带有时间字段的数据,需求是对数据进行汇总统计和简单分析处理(一般而言,数据量巨大的需求处理逻辑都不会特别复杂)。所以,虽然标题称之为大数据,但实际上也没有特别夸张。
01 大数据读取
pandas自带了常用文件的读取方法,例如csv文件对应的读取函数即为pd.read_csv,这也是日常应用中经常接触的方法。然而对于处理这个50G的csv文件而言,直接使用是肯定不行的,当前个人电脑内存普遍在8G-16G内存之间,笔者的是一台8G内存的工作机,除去系统占用基本留给用于加载数据的空间不到6G,另一方面通过多次试验结果:对于一个2G的文件,读取过程中内存占用会达到4G左右,大概是实际文件体积的两倍,加载完毕之后会有有所回落。所以,就8G内存的工作机而言,读取一个2.5G的大文件本身已经存在一定风险。
为此,pandas开发者专为此设计了两组很有用的参数,分别用于控制行和列信息:
- skiprows + nrows,前者用于控制跳过多少行记录,后者用于控制读取行数,skiprows默认值为0,nrows缺省读取所有行数,这也是最常用的方式。但合理的设置两个参数,可以实现循环读取特定范围的记录
- usecols:顾名思义,仅加载文件中特定的列字段,非常适用于列数很多而实际仅需其中部分字段的情况,要求输入的列名实际存在于表中
pd.read_csv()中相关参数说明
具体到实际需求,个人实现时首先通过循环控制skiprows参数来遍历整个大文件,每次读取后对文件再按天分割,同时仅选取其中需要的3个列字段作为加载数据,如此一来便实现了大表到小表的切分。虽然受限于内存而执行效率有限,但也终究算是一种解决方案。
02 内存管理
严格来说,这可能并不是大数据处理中才涉及到的问题,而是由Python的变量管理特性决定的。不同于C++中的手动回收、Java中的自动回收,Python中的对象采用引用计数管理,当计数为0时内存回收。所以,如果当一个变量不再需要使用时,最简单的办法是将其引用数-1,以加速其内存回收。有一定python基础的读者可能会想到用关键字del实现,这个思路是对的,但有时还不够保险和彻底,更为靠谱的方案是del + gc.collect()显式回收。这里gc是一个python内置模块,用于内存回收,gc = garbage collect。
仍然是循环读取大文件分表的问题,对于每次循环,读取一个大文件到内存,执行完相应处理流程后,显式执行以下两行代码即可,实测效果很有用。
del xx
gc.collect()
03 时间字段的处理
给定的大文件中,时间字段是一个包含年月日时分秒的字符串列,虽然在read_csv方法中自带了时间解析参数,但对于频繁多次应用时间列进行处理时,其实还有更好的方法:转为时间戳。
例如,在个人的实际处理中主要用到的操作包括:按时间排序、按固定周期进行重采样、分组聚合统计等,这几个操作中无一例外都涉及到时间列的比较,如果是字符串格式或者时间格式的时间列,那么在每次比较中实际要执行多次比较,而如果转换为时间戳后,则参与比较的实际上是一个整数值,毫无疑问这是效率最高的比较类型。进一步地,对于重采样需求而言,还可以通过整除特定的时间间隔,然后执行groupby操作即可。例如,执行每5分钟重采样,则可将所有时间戳(秒级)整除300,然后以相应结果作为groupby字段即可。
这里,补充两种将时间格式转换为时间戳的具体实现方法:
# 假设df['dt']列是时间格式,需将其转换为时间戳格式
# 方法一:
df['dt'] = (pd.to_datetime(df['dt'])-pd.to_datetime('1970-01-01T08:00:00')).total_seconds()
# 方法二:
df['dt'] = (pd.to_datetime(df['dt']).values - np.datetime64('1970-01-01T08:00:00Z')) // np.timedelta64(1, 's')
- python中numpy模块下的np.clip()的用法
- Leetcode-Easy 543. Diameter of Binary Tree
- Leetcode-Easy 572. Subtree of Another Tree
- 图解javascript this指向什么?
- 2017/6/8-python正则表达式的使用
- 洛谷P1306 斐波那契公约数
- Angular开发实践(二):HRM运行机制
- Angular开发实践(一):环境准备及框架搭建
- 洛谷P2818 天使的起誓
- 连续子数组的最大和
- 10.25解题报告
- React第三方组件5(状态管理之Redux的使用①简单使用)
- JavaScript设计模式与开发实践 - 观察者模式
- React第三方组件4(状态管理之Reflux的使用⑤异步操作)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Jmeter 常用函数(31)- 详解 __iterationNum
- Jmeter 常用函数(30)- 详解 __if
- Jmeter 常用函数(29)- 详解 __eval
- Jmeter 常用函数(28)- 详解 __FileToString
- Jmeter 常用函数(26)- 详解 __chooseRandom
- Jmeter 常用函数(25)- 详解 __V
- Java8新特性
- 线程池基础
- 多线程基础
- 02 Spring Boot整合JDBC
- 01 Spring Boot 入门
- Spring——单例Bean中使用多例Bean
- DI——手动注入
- bean scope——bean的作用域及注意点
- IDEA启动tomcat乱码问题