各位观众老爷们,大家好!今天咱不开车,来聊聊Pandas DataFrame里的“潜规则”——Block Manager和Index的内存优化。别害怕,听名字唬人,其实就是教你省钱(内存)的小技巧。
Part 1: DataFrame的“骨架”:Block Manager
咱们先来扒一扒DataFrame的皮,看看它的骨架长啥样。 你可能觉得DataFrame就是一个表格,但实际上,Pandas为了提高效率,把不同类型的数据分成了不同的“块”(Blocks)来存储。
举个例子,你有一个DataFrame,既有整数,又有浮点数,还有字符串,那么Pandas就会把它分成三个Block:一个存整数,一个存浮点数,一个存字符串。 这就是Block Manager的核心思想:同类型的数据住一起,方便管理和运算。
import pandas as pd
import numpy as np
# 创建一个混合类型的DataFrame
df = pd.DataFrame({
'A': np.arange(5, dtype='int64'),
'B': pd.array([True, False, True, False, True], dtype='boolean'),
'C': pd.Series(list('abcde'), dtype='string'),
'D': np.random.randn(5),
'E': pd.Categorical(['foo', 'bar', 'foo', 'bar', 'foo'])
})
print(df)
print(df.dtypes)
输出结果(类似):
A B C D E
0 0 True a 0.469112 foo
1 1 False b -0.282863 bar
2 2 True c -1.509059 foo
3 3 False d -1.135632 bar
4 4 True e 1.212112 foo
A int64
B boolean
C string
D float64
E category
dtype: object
在这个例子中,DataFrame df
内部可能被分成多个Block:一个存int64的A列,一个存boolean的B列,一个存string的C列,一个存float64的D列,还有一个存Categorical的E列。 具体的分块方式取决于Pandas内部的优化策略,但总的原则是:尽量把相同类型的数据放在一起。
Block Manager的好处:
- 类型优化: 相同类型的数据可以采用更高效的存储方式,例如整数可以用
int8
、int16
等更小的类型存储,节省内存。 - 向量化运算: 同类型的数据可以进行向量化运算,提高计算速度。
- 内存共享: 如果多个DataFrame共享同一列数据(例如通过切片),那么这些DataFrame可以共享同一个Block,避免重复存储。
如何查看DataFrame的Block结构?
虽然Pandas没有直接提供公开的API来查看DataFrame的Block结构,但我们可以通过一些“黑科技”来间接观察。
def get_blocks(df):
"""
获取DataFrame的Block信息
"""
blocks = getattr(df, '_mgr').blocks
for i, block in enumerate(blocks):
print(f"Block {i}:")
print(f" Type: {block.dtype}")
print(f" Shape: {block.shape}")
print(f" Columns: {df.columns[block.mgr_locs.as_array()]}")
print("-" * 20)
get_blocks(df)
输出结果(类似):
Block 0:
Type: int64
Shape: (5, 1)
Columns: Index(['A'], dtype='object')
--------------------
Block 1:
Type: bool
Shape: (5, 1)
Columns: Index(['B'], dtype='object')
--------------------
Block 2:
Type: string
Shape: (5, 1)
Columns: Index(['C'], dtype='object')
--------------------
Block 3:
Type: float64
Shape: (5, 1)
Columns: Index(['D'], dtype='object')
--------------------
Block 4:
Type: category
Shape: (5, 1)
Columns: Index(['E'], dtype='object')
--------------------
这个函数使用了DataFrame的内部属性_mgr
来访问Block Manager,然后遍历所有的Block,打印出它们的类型、形状和包含的列名。 注意:_mgr
是Pandas的内部属性,不建议直接使用,因为可能会在未来的版本中发生变化。
Part 2: Index的“小心机”:内存优化
Index是DataFrame的“身份证”,用于标识每一行和每一列。 Pandas的Index有很多种类型,例如Int64Index
、Float64Index
、DatetimeIndex
、CategoricalIndex
等等。 不同的Index类型在存储和查询效率上有所不同。
Index的内存占用:
Index的内存占用不容忽视。 特别是当DataFrame非常大时,Index可能会占用大量的内存。
# 创建一个大的DataFrame
n = 1000000
df = pd.DataFrame({'A': range(n), 'B': np.random.randn(n)})
# 查看DataFrame的内存占用
print(df.memory_usage(deep=True))
# 查看Index的内存占用
print(df.index.memory_usage(deep=True))
输出结果(类似):
Index 128
A 8000000
B 8000000
dtype: int64
8000128
可以看到,Index也占用了不少内存。
Index的优化技巧:
-
选择合适的Index类型:
- Int64Index: 默认的整数索引,适用于大多数情况。
- RangeIndex: 如果Index是连续的整数序列,可以使用
RangeIndex
,它只存储起始值和步长,可以大大节省内存。 - CategoricalIndex: 如果Index是有限的、重复的值,可以使用
CategoricalIndex
,它会将Index转换为类别编码,节省内存。 - DatetimeIndex: 专门用于存储时间序列数据,提供了丰富的日期时间操作。
-
使用
set_index
和reset_index
:set_index
可以将DataFrame的某一列设置为Index。reset_index
可以将Index重置为默认的Int64Index
,并将原来的Index转换为DataFrame的一列。
这两个函数可以用来调整DataFrame的Index结构,从而优化内存占用。
- 使用
astype
转换Index类型:
可以尝试将Index转换为更小的数据类型,例如将Int64Index
转换为Int32Index
或Int16Index
,如果你的数据范围允许的话。 - 减少Index的层级:
对于MultiIndex,层级越多,内存占用越大。 尽量减少MultiIndex的层级,或者考虑使用其他方式来表示多层索引关系。
例子:将Int64Index转换为RangeIndex
# 创建一个DataFrame
df = pd.DataFrame({'A': range(1000000), 'B': np.random.randn(1000000)})
# 查看DataFrame的内存占用
print("Original DataFrame memory usage:")
print(df.memory_usage(deep=True))
print(f"Original Index memory usage: {df.index.memory_usage(deep=True)}")
# 将Index转换为RangeIndex
df = df.set_index('A') # 先将'A'列设为索引, 这样原来的数字索引才会被重置
df = df.reset_index(drop=True) # 重置索引,drop=True表示丢弃原来的索引列
# 查看DataFrame的内存占用
print("nDataFrame memory usage after converting to RangeIndex:")
print(df.memory_usage(deep=True))
print(f"New Index memory usage: {df.index.memory_usage(deep=True)}")
输出结果(类似):
Original DataFrame memory usage:
Index 128
A 8000000
B 8000000
dtype: int64
Original Index memory usage: 8000128
DataFrame memory usage after converting to RangeIndex:
Index 128
B 8000000
dtype: int64
New Index memory usage: 128
可以看到,将Index转换为RangeIndex
后,Index的内存占用大大减少。 drop=True
参数确保了原来的 ‘A’ 列(现在是旧的索引)不会作为新的一列添加回 DataFrame。
例子:使用CategoricalIndex
# 创建一个DataFrame
data = {'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Alice', 'Bob'],
'Age': [25, 30, 28, 22, 25, 30]}
df = pd.DataFrame(data)
# 将Name列设置为Index
df = df.set_index('Name')
print("Memory usage with default Index:")
print(df.index.memory_usage())
# 将Index转换为CategoricalIndex
df.index = pd.CategoricalIndex(df.index)
print("nMemory usage with CategoricalIndex:")
print(df.index.memory_usage())
输出结果(类似):
Memory usage with default Index:
48
Memory usage with CategoricalIndex:
176
在这个例子中,CategoricalIndex
实际上可能占用了更多内存(因为引入了categories的存储)。 但是,当Index包含大量重复值时,CategoricalIndex
的优势会更加明显。 关键在于根据数据的具体情况选择合适的Index类型。
Part 3: 数据类型的“精打细算”:数据类型的优化
除了Block Manager和Index,DataFrame的数据类型也会影响内存占用。 Pandas提供了多种数据类型,例如int8
、int16
、int32
、int64
、float16
、float32
、float64
等等。 选择合适的数据类型可以大大节省内存。
数据类型的选择原则:
- 整数类型: 根据数据的范围选择合适的整数类型。 如果数据范围在-128到127之间,可以使用
int8
;如果在-32768到32767之间,可以使用int16
;以此类推。 - 浮点数类型: 如果精度要求不高,可以使用
float32
代替float64
,节省一半的内存。 - 字符串类型: 尽量使用
string
类型(Pandas 1.0新增),而不是object
类型。string
类型可以更好地利用内存,并提供更高效的字符串操作。 - Categorical类型: 如果某一列包含有限的、重复的值,可以使用
Categorical
类型,它会将数据转换为类别编码,节省内存。
例子:优化数据类型
# 创建一个DataFrame
df = pd.DataFrame({
'A': np.arange(1000, dtype='int64'),
'B': np.random.randn(1000),
'C': ['foo', 'bar'] * 500
})
# 查看DataFrame的内存占用
print("Original DataFrame memory usage:")
print(df.memory_usage(deep=True))
# 优化数据类型
df['A'] = df['A'].astype('int16')
df['B'] = df['B'].astype('float32')
df['C'] = df['C'].astype('category')
# 查看DataFrame的内存占用
print("nDataFrame memory usage after optimization:")
print(df.memory_usage(deep=True))
输出结果(类似):
Original DataFrame memory usage:
Index 128
A 8000
B 8000
C 64000
dtype: int64
DataFrame memory usage after optimization:
Index 128
A 2000
B 4000
C 2120
dtype: int64
可以看到,优化数据类型后,DataFrame的内存占用大大减少。
总结:
今天的讲座就到这里。 咱们学习了DataFrame的Block Manager、Index和数据类型的优化技巧,这些技巧可以帮助你更好地管理DataFrame的内存,提高程序的性能。
记住,内存就像钱,能省则省。 只有精打细算,才能让你的程序跑得更快、更稳。 下次再见!