Pandas 处理数据的效率还是很优秀的,相对于大规模的数据集只要掌握好正确的方法,就能让在数据处理时间上节省很多很多的时间。
Pandas 是建立在 NumPy 数组结构之上的,许多操作都是在 C 中执行的,要么通过 NumPy,要么通过 Pandas 自己的 Python 扩展模块库,这些模块用 Cython 编写并编译为 C。理论上来说处理速度应该是很快的。
那么为什么同样一份数据由2个人处理,在设备相同的情况下处理时间会出现天差地别呢?
整套学习自学教程中应用的数据都是《三國志》、《真·三國無雙》系列游戏中的内容。
需要明确的是这不是关于如何过度优化 Pandas 代码的指南。如果使用得当 Pandas 已经构建为可以快速运行。此外优化和编写干净的代码之间存在很大差异。
import pandas as pd
df = pd.read_excel("Romance of the Three Kingdoms 13/人物历史登入数据.xlsx",)
df.head()
由于DataFrame时间戳的限制,因此在年份前面加上2符合范围。
pd.Timestamp.min
Timestamp('1677-09-21 00:12:43.145225')
pd.Timestamp.max
Timestamp('2262-04-11 23:47:16.854775807')
import re
from datetime import datetime
def clean_date_str(text):
# 返回处理的年月数据
data = re.findall(r'\d{3}年\s\d{1,2}月', text)[0].replace(" ","")
return "2"+data
df["date_str"] = df["シナリオタイトル"].apply(clean_date_str)
def clean_date_time(text):
return datetime.strptime(t2,'%Y年%m月')
df["date_str"] = df["シナリオタイトル"].apply(clean_date)
df["date_time"] = df["date_str"].apply(clean_date_time)
乍一看这看起来不错,但有一个小问题。 Pandas 和 NumPy 有一个 dtypes(数据类型)的概念。 如果未指定任何参数,则 date_time 将采用 object dtype。
df.dtypes
......
date_str object
date_time object
dtype: object
type(df.iat[-1, 0])
str
object 不仅是 str 的容器,而且是任何不能完全适合一种数据类型的列的容器。将日期作为字符串处理会既费力又低效(这也会导致内存效率低下)。为了处理时间序列数据,需要将 date_time 列格式化为日期时间对象数组( Timestamp)。
df['date_time'] = pd.to_datetime(df['date_time'])
df['date_time'].dtype
dtype('
现在有一个名为 df 的 DataFrame,有两列和一个用于引用行的数字索引 date_time 。
df.head()
使用 Jupyter 自带的 %%time 计时装饰器进行测试。
def convert(df, column_name):
return pd.to_datetime(df[column_name])
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 2.99 ms
def convert_with_format(df, column_name):
return pd.to_datetime(df[column_name],format='%d/%m/%y %H:%M')
%%time
df['date_time'] = convert(df, 'date_time')
Wall time: 995 µs
处理效率提高3倍之多。如果在处理大规模数据的情况下,处理数据的时间会无限的放大。
pandas 中可以使用简单的数据循环进行计算,比如我们按照年龄给人物标定对应的标签。
def apply_age(age):
if age<=10:
return "外傅之年"
elif 10 < age <= 20:
return "弱冠之年"
elif 20 < age <= 30:
return "而立之年"
elif 30 < age <= 40:
return "不惑之年"
elif 40 < age <= 50:
return "天命之年"
elif 50 < age <= 60:
return "花甲之年"
elif 60 < age <= 70:
return "古稀之年"
elif 70 < age <= 80:
return "耄耋之年"
elif 80 < age <= 90:
return "鲐背之年"
elif 90 < age <= 100:
return "期颐之年"
else:
return "-"
由于 年龄 列数据类型是 str 因此需要进行转换处理。
df["年齢"] = df["年齢"].replace(["-"],["101"])
df["年齢"] = df["年齢"].astype(int)
df["年齢_タイトル"] = df["年齢"].apply(apply_age)
df
Wall time: 5.97 ms
Pandas 实际上 for i in range(len(df)) 通过引入 DataFrame.itertuples() 和 DataFrame.iterrows() 方法使语法就可能显得多余,这些都是yield一次一行的生成器方法。
%%time
age_str_list = []
for index, row in df.iterrows():
age = row['年齢']
age_str = apply_age(age)
age_str_list.append(age_str)
df["年齢_タイトル_iter"] = age_str_list
Wall time: 1.32 s
速度提高又4倍之多。
可以使用 .apply() 方法进一步改进此操作。 Pandas 的 .apply() 方法采用函数(可调用对象)并将它们沿 DataFrame 的轴(所有行或所有列)应用。
lambda 函数将两列数据传递给 apply_tariff()。
%%time
df["年齢_タイトル_apply"] = df.apply(lambda row:apply_age(row['年齢']),axis=1)
Wall time: 219 ms
.apply() 的语法优势很明显,代码简洁、易读、明确。在这种情况下所用时间大约是该 .iterrows() 方法的4分之一。
但是如何在 Pandas 中将条件计算应用为矢量化操作呢?一个技巧是根据的条件选择和分组 DataFrame 的部分,然后对每个选定的组应用矢量化操作。
使用 Pandas 的.isin()方法选择行,然后在矢量化操作中应用。
%%time
df["年齢_タイトル_isin"] = ""
age_1 = df["年齢"].isin(range(0, 11))
age_2 = df["年齢"].isin(range(11, 21))
age_3 = df["年齢"].isin(range(21, 31))
age_4 = df["年齢"].isin(range(31, 41))
age_5 = df["年齢"].isin(range(41, 51))
age_6 = df["年齢"].isin(range(51, 61))
age_7 = df["年齢"].isin(range(61, 71))
age_8 = df["年齢"].isin(range(71, 81))
age_9 = df["年齢"].isin(range(81, 91))
age_10 = df["年齢"].isin(range(91, 101))
age_11 = df["年齢"].isin(range(101, 999))
df.loc[age_1, '年齢_タイトル_isin'] = "外傅之年"
df.loc[age_2, '年齢_タイトル_isin'] = "弱冠之年"
df.loc[age_3, '年齢_タイトル_isin'] = "而立之年"
df.loc[age_4, '年齢_タイトル_isin'] = "不惑之年"
df.loc[age_5, '年齢_タイトル_isin'] = "天命之年"
df.loc[age_6, '年齢_タイトル_isin'] = "花甲之年"
df.loc[age_7, '年齢_タイトル_isin'] = "古稀之年"
df.loc[age_8, '年齢_タイトル_isin'] = "耄耋之年"
df.loc[age_9, '年齢_タイトル_isin'] = "鲐背之年"
df.loc[age_10, '年齢_タイトル_isin'] = "期颐之年"
df.loc[age_11, '年齢_タイトル_isin'] = "-"
Wall time: 22.9 ms
其中整个过程方法返回一个布尔序列。
age_1
0 False
1 True
2 True
3 True
4 True
...
15895 True
15896 True
15897 False
15898 False
15899 False
Name: 年齢, Length: 15900, dtype: bool
设置时间切分的列表和对那个计算的函数公式,让操作起来更简单,但是这个对于新手来说代码阅读起来有些困难。
%%time
df["年齢_タイトル_cut"] = pd.cut(x=df["年齢"],
bins=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100,999],
include_lowest=True,
labels=[ "外傅之年","弱冠之年","而立之年", "不惑之年", "天命之年", "花甲之年", "古稀之年", "耄耋之年","鲐背之年","期颐之年","-"]
)
Wall time: 3.99 ms
Pandas Series 和 DataFrames 是在 NumPy 库之上设计的。这为提供了更大的计算灵活性,因为 Pandas 可以与 NumPy 数组和操作无缝协作。
使用 NumPy 的digitize()函数。它与 Pandas 的相似之处cut()在于数据将被分箱,但这次它将由一个索引数组表示,该数组表示每个小时属于哪个箱。然后将这些索引应用于价格数组。
import numpy as np
%%time
age = np.array([ "外傅之年","弱冠之年","而立之年", "不惑之年", "天命之年", "花甲之年", "古稀之年", "耄耋之年","鲐背之年","期颐之年","-"])
age_str = np.digitize(df["年齢"], bins=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
temp_dict = {}
for k,v in zip(list(set(age_str)),age):
temp_dict[k]=v
df["年齢_タイトル_np"] = [temp_dict[n] for n in age_str]
Wall time: 4.99 ms
对比一下上面几种不同的处理方式的效率吧。
功能 | 运行时间(秒) |
apply_tariff_loop() | 5.97 ms |
apply_tariff_iterrows() | 1.32 s |
apply_tariff_withapply() | 219 ms |
apply_tariff_isin() | 22.9 ms |
apply_tariff_cut() | 3.99 ms |
apply_tariff_digitize() | 4.99 ms |
通常构建复杂的数据模型时,对数据进行一些预处理会很方便。可以在这里做的一件非常有用的事情是预处理,然后以处理后的形式存储数据,以便在需要时使用。但是如何才能以正确的格式存储数据而无需再次重新处理呢?
Pandas 有一个内置的解决方案使用 HDF5,一种专为存储表格数据数组而设计的高性能存储格式。Pandas 的 HDFStore 类允许将 DataFrame 存储在 HDF5 文件中,以便可以有效地访问它,同时仍保留列类型和其他元数据。dict 是一个类似字典的类,因此可以像对 Python对象一样进行读写。
将预处理的耗电量 DataFrame 存储df在 HDF5 文件中。
data_store = pd.HDFStore('processed_data.h5')
# 将 DataFrame 放入对象中,将键设置为 preprocessed_df
data_store['preprocessed_df'] = df
data_store.close()
从 HDF5 文件访问数据的方法,并保留数据类型。
data_store = pd.HDFStore('processed_data.h5')
preprocessed_df = data_store['preprocessed_df']
data_store.close()
留言与评论(共有 0 条评论) “” |