刚接触Pandas那会儿,我习惯直接读CSV文件,然后就开始各种操作。数据量小的时候还好,一旦上了几百万行,内存直接飙红。机器风扇嗡嗡响,隔壁同事都来问我搞什么大项目。后来发现,问题出在数据类型上。Pandas默认给每列分配的类型,不一定是最省内存的。
检查dtypes是第一步。用df.info()能看到每列占用多少内存。经常看到object类型占了大头,尤其是那些像“男”“女”这种重复值多的列。把object转成category能省一半甚至更多内存。操作很简单,df['gender'] = df['gender'].astype('category')。注意,category类型适合分类数量远小于数据行数的情况。如果每行都是唯一值,转category反而会让内存变大。
数值列也有坑。Pandas读整数默认用int64,8个字节。如果你的数据范围很小,比如年龄0到120,完全可以用int8,1个字节。省8倍内存。用pd.to_numeric(..., downcast='integer')或者手动指定astype('int8'),内存就下来了。遇到浮点数也是同理,float64换成float32,肉眼可见的内存下降。
读文件时别偷懒,直接在read_csv里指定dtype。先拿一小部分数据预览,确定每列实际范围。然后写好dtype字典,读大文件时一次性加载。比如pd.read_csv('data.csv', dtype={'age':'int8', 'salary':'float32'})。这样就不会先占满内存再转换了。我试过几次,内存占用直接降了60%。
时间列也别忽略。Pandas默认把时间解析成object,非常占内存。用parse_dates=True或者手动指定pd.to_datetime(),转成datetime64类型。64位比字符串省太多。如果时间精度不需要到毫秒,还可以用datetime64[s]只保留秒。这一步能让时间列内存减半。
还有一种情况是数据里有大量空值。Pandas的NaN在数值列里会占用内存,因为NaN本身是浮点数。如果你用整数列,遇到NaN就会整列转成float64。这时候可以用pd.NA或者Nullable整数类型如Int8。注意这里的I是大写。这样既能保持整数类型又能存缺失值,内存比float64省很多。
去掉无关列是原始但有效的办法。有时候一份数据表里几十列,实际用到的只有几列。读文件时直接用usecols参数只加载需要的列。数据量大的时候,少加载一列就少几十M内存。别等读完全部再drop,那样既慢又占资源。
我遇到过最头疼的情况是重复索引。Pandas的索引默认从0开始,但如果手动设了索引列并且不连续,内存里会多存一份索引映射。用reset_index(drop=True)重新生成连续索引,能释放这部分内存。如果你在循环里拼接数据框,记得在最终结果上做一次reset_index。
那些让你头疼的字符串列,如果只是做分类或聚合,提前转category。如果字符串很长但重复少,考虑存成hash值。用pd.util.hash_array可以把字符串转成定长整数,聚合和join一样快。但要注意哈希冲突的概率很低,但存在。如果你不放心就别用。
分组操作前先压缩数据内存。用groupby的时候,Pandas需要读入整个数据框到内存。如果能把数据压缩到原来的三分之一,groupby也能快很多。我习惯在写复杂查询前,先跑一遍info看看内存,然后批量转换类型。这步花30秒,后面能省半小时。
数据写入时也注意。保存为feather或parquet格式,比CSV小而且读得快。这些格式支持压缩,并且能保留dtype设置。下次读回来不用再重复转换类型。用df.to_parquet('data.parquet'),文件大小能缩到CSV的十分之一。配合分区存储,大型项目也能轻松处理。
说到底,管理Pandas内存就像整理房间。定期清理不用的东西,把杂物归归类。别等屋满人危的时候再着急。养成检查数据类型的习惯,写代码时多花一分钟,运行时就省十分钟。两年下来,我现在看到别人用Pandas卡顿,就知道他肯定没管过dtype。这个细节,让我从每天和内存打架变成了安静写代码的人。