在机器学习领域,Python因为其兼具对用户友好和功能强大两个特点,是最热门的语言之一。但在Python初步入门时,可能会遇到运行很慢的问题。尤其当数据量很大时,某些操作会降低整个项目的效率。今天我来聊聊我在Python入门时的一些体会。
![]()
Pandas读取数据时,谁吃掉了我的内存?
1)Numpy Array vs Python List
Pandas是基于Numpy的数组array构建的,大家最常使用的库之一。在导入数据时,大家有时会遇到内存占用过大的问题,但是如果注意一些小技巧,可以将内存占用大大降低。在具体聊怎么操作之前,我们先看一下为什么要使用Pandas处理数据。
![]()
Figure 1: Numpy Array vs Python List 存储形式图。
图片来源:https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/
Fig. 1图右是一个Numpy Array的存储形式图, 图左是一个Python List的存储形式图。相比于Python将数据存放在不连续的区域,Numpy在创建array的时候,是寻找内存上的一连串区域来存放。因此在批量处理数据时,Numpy只需要在这一片连续的区域内走动,就可以实现快速索引,从而比用Python List处理数据高效得多。
2)优化Pandas的object类型
回到Pandas读取数据,这里我随便选取了一个样本数据,数据内容不必在意,操作都是在jupyter notebook中进行的。首先导入数据,并用info查看内存占用情况。
- 1import pandas as pd
- 2data = pd.read_csv('data.csv', delimiter=',',encoding='utf-8')
- 3data.info( verbose = False, memory_usage='deep')
复制代码- 1
- 2RangeIndex: 1602282 entries, 0 to 1602281
- 3Columns: 96 entries, col1 to col96
- 4dtypes: float64(58), int64(30), object(8)
- 5memory usage: 1.9 GB
复制代码 这里我们看到data中有58列float64类型,30列int64类型以及8列object类型。在Pandas中,每种数据类型都是分开存储的,如果分别看这几类数据占了多少内存,就会发现仅8列object类型,就占用了接近50%(894.MB/1.9G)的内存消耗。
- 1data.select_dtypes('object').info(verbose=False, memory_usage='deep')
复制代码- 1
- 2RangeIndex: 1602282 entries, 0 to 1602281
- 3Columns: 8 entries, obj1 to obj8
- 4dtypes: object(8)
- 5memory usage: 895.4 MB
复制代码 object类型是个什么意思呢,它就是表示使用Python内置类型存储字符串数据。于是我们又看到了这张熟悉的结构图。Fig. 2左边是以NumPy数据类型存储数值数据结构图,右边是使用Python内置类型存储字符串数据结构图。
![]()
Figure 2: Pandas中数值型和字符串型数据存储形式图
图片来源:https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/
当使用object类型时(Fig. 2左),尽管每个指针只占用1字节的内容,但如果每个字符串在 Python 中都是单独存储的,那就会占用实际字符串那么大的空间。其实我们接触的字符型数据,往往只有有限的unique values,比如性别变量只有男,女,缺失三种情况。针对这种数据,如果将object类型转化成category类型,Pandas就使用最节省空间的int子类型来表示该列中的所有不同值,从而可以大大减少内存占用。
- 1for col in ['obj1', 'obj2', 'obj3', 'obj4']:
- 2 data[col] = data[col].astype('category')
- 3 data.info(verbose=False, memory_usage='deep')
复制代码- 1
- 2RangeIndex: 1602282 entries, 0 to 1602281
- 3Columns: 96 entries, col1 to col96
- 4dtypes: category(4), float64(58), int64(30), object(4)
- 5memory usage: 1.5 GB
复制代码 我们看到仅仅是将样本数据中的4列object类型转化为category类型后,内存就从1.9GB降到了1.5GB。如果你查看这几列数据,除了这几列的数据类型变了之外,数据看起来是完全一样的。
观察一下我们的样本数据,剩下的object列中存储的都是日期。对于日期数据,可以转化成datetime类型。Pandas中datetime类型是64位的,所以如果你的日期变量是以数值型存储,那转换成datetime也许并没有内存优势。但还是建议转换成datetime,因为这样日期方面的操作会变得很简单。一个小技巧,在进行datetime转换时,不指定日期格式也可以,但是指定日期格式会极大缩短转换操作时间,其中具体原因这里就不解释了。
- 1data['date'] = pd.to_datetime(date, format='%Y%m%d')
- 2data.info(verbose=False, memory_usage='deep')
复制代码- 1
- 2RangeIndex: 1602282 entries, 0 to 1602281
- 3Columns: 96 entries, col1 to col96
- 4dtypes: category(4), datetime64[ns](4), float64(58), int64(30)
- 5memory usage: 1.1 GB
复制代码 再次查看一下内存使用,我们看到内存占用又降低了0.4GB。所以在对object类型优化后,内存占用从1.9GB降低到1.1GB,一共降低了0.8GB。
除了对object类型优化,还可以对数值型数据优化。比如当正整数变量的类型是有符号整数时,可以将其设为无符号整数类,数值范围很小的变量从int64转换为int32等等,这里就不讨论了。
![]()
矢量化操作
当我们对Pandas进行一些操作时,如果你抱怨某个操作花费了很多时间,有可能是你使用了不必要的循环。比如对我们刚刚的样本数据,想根据列login1和createtime的以下规则,增加新的一列new_col:
createtime
new_col
0 |
|