正文
return
f'
{df.memory_usage(deep=
True
).sum() /
1024
**
2
:
3.2
f}
MB'
def
convert_df
(df: pd.DataFrame, deep_copy: bool = True)
-> pd.DataFrame:
"""Automatically converts columns that are worth stored as ``categorical`` dtype. Parameters ---------- df: pd.DataFrame Data frame to convert. deep_copy: bool Whether or not to perform a deep copy of the original data frame. Returns ------- pd.DataFrame Optimized copy of the input data frame. """
return
df.copy(deep=deep_copy).astype({ col:
'category'
for
col
in
df.columns
if
df[col].nunique() / df[col].shape[
0
] <
0.5
})
Pandas 提出了一种叫做 memory_usage() 的方法,这种方法可以分析数据框的内存消耗。在代码中,指定 deep=True 来确保考虑到了实际的系统使用情况。
memory_usage():https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.memory_usage.html
了解列的类型(https://pandas.pydata.org/pandas-docs/stable/getting_started/basics.html#basics-dtypes)很重要。它可以通过两种简单的方法节省高达 90% 的内存使用:
除了降低数值类型的大小(用 int32 而不是 int64)外,Pandas 还提出了分类类型:
https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html
如果你是用 R 语言的开发人员,你可能觉得它和 factor 类型是一样的。
这种分类类型允许用索引替换重复值,还可以把实际值存在其他位置。教科书中的例子是国家。和多次存储相同的字符串「瑞士」或「波兰」比起来,为什么不简单地用 0 和 1 替换它们,并存储在字典中呢?
categorical_dict = {0: 'Switzerland', 1: 'Poland'}
Pandas 做了几乎相同的工作,同时添加了所有的方法,可以实际使用这种类型,并且仍然能够显示国家的名称。
回到 convert_df() 方法,如果这一列中的唯一值小于 50%,它会自动将列类型转换成 category。这个数是任意的,但是因为数据框中类型的转换意味着在 numpy 数组间移动数据,因此我们得到的必须比失去的多。
>>> mem_usage(df)
10.28 MB
>>> mem_usage(df.set_index(['country', 'year', 'sex', 'age']))
5.00
MB
>>> mem_usage(convert_df(df))
1.40 MB
>>> mem_usage(convert_df(df.set_index(['country', 'year', 'sex', 'age'])))
1.40 MB
通过使用「智能」转换器,数据框使用的内存几乎减少了 10 倍(准确地说是 7.34 倍)。
Pandas 是强大的,但也需要付出一些代价。当你加载 DataFrame 时,它会创建索引并将数据存储在 numpy 数组中。这是什么意思?一旦加载了数据框,只要正确管理索引,就可以快速地访问数据。
访问数据的方法主要有两种,分别是通过索引和查询访问。根据具体情况,你只能选择其中一种。但在大多数情况中,索引(和多索引)都是最好的选择。我们来看下面的例子:
>>> %%time
>>> df.query('country == "Albania" and year == 1987 and sex == "male" and age == "25-34 years"')
CPU times: user 7.27 ms, sys: 751 µs, total: 8.02 ms
# ==================
>>> %%time
>>> mi_df.loc['Albania', 1987, 'male', '25-34 years']
CPU times: user 459 µs, sys: 1 µs, total: 460 µs
%%time
mi_df = df.set_index(['country', 'year', 'sex', 'age'])
CPU times: user 10.8 ms, sys: 2.2 ms, total: 13 ms
通过查询访问数据的时间是 1.5 倍。如果你只想检索一次数据(这种情况很少发生),查询是正确的方法。否则,你一定要坚持用索引,CPU 会为此感激你的。
.set_index(drop=False) 允许不删除用作新索引的列。
.loc[]/.iloc[] 方法可以很好地读取数据框,但无法修改数据框。如果需要手动构建(比如使用循环),那就要考虑其他的数据结构了(比如字典、列表等),在准备好所有数据后,创建 DataFrame。否则,对于 DataFrame 中的每一个新行,Pandas 都会更新索引,这可不是简单的哈希映射。
>>> (pd.DataFrame({'a':range(2), 'b': range(2)}, index=['a', 'a']) .loc['a'])
a b
a 0 0
a 1 1
因此,未排序的索引可以降低性能。为了检查索引是否已经排序并对它排序,主要有两种方法:
%%time
>>> mi_df.sort_index()
CPU times: user 34.8 ms, sys: 1.63 ms, total: 36.5 ms
>>> mi_df