数据科学必备Pandas数据处理加速技巧汇总

Pandas 处理数据的效率还是很优秀的,相对于大规模的数据集只要掌握好正确的方法,就能让在数据处理时间上节省很多很多的时间。

Pandas 是建立在 NumPy 数组结构之上的,许多操作都是在 C 中执行的,要么通过 NumPy,要么通过 Pandas 自己的 Python 扩展模块库,这些模块用 Cython 编写并编译为 C。理论上来说处理速度应该是很快的。

那么为什么同样一份数据由2个人处理,在设备相同的情况下处理时间会出现天差地别呢?

整套学习自学教程中应用的数据都是《三國志》、《真·三國無雙》系列游戏中的内容。

数据科学必备Pandas数据处理加速技巧汇总


需要明确的是这不是关于如何过度优化 Pandas 代码的指南。如果使用得当 Pandas 已经构建为可以快速运行。此外优化和编写干净的代码之间存在很大差异。


数据准备

import pandas as pd

df = pd.read_excel("Romance of the Three Kingdoms 13/人物历史登入数据.xlsx",)
df.head()


数据科学必备Pandas数据处理加速技巧汇总


日期时间数据优化


数据科学必备Pandas数据处理加速技巧汇总

由于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数据处理加速技巧汇总


乍一看这看起来不错,但有一个小问题。 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()


数据科学必备Pandas数据处理加速技巧汇总


使用 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数据处理加速技巧汇总


循环 .itertuples() 和 .iterrows() 方法

Pandas 实际上 for i in range(len(df)) 通过引入 DataFrame.itertuples()DataFrame.iterrows() 方法使语法就可能显得多余,这些都是yield一次一行的生成器方法。

  • .itertuples() 为每一行生成一个命名元组,行的索引值作为元组的第一个元素。 名称元组是来自 Python 集合模块的数据结构,其行为类似于 Python 元组,但具有可通过属性查找访问的字段。
  • .iterrows() 为 DataFrame 中的每一行生成 (index, Series) 对(元组)。
%%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


数据科学必备Pandas数据处理加速技巧汇总

速度提高又4倍之多。

.apply() 方法

可以使用 .apply() 方法进一步改进此操作。 Pandas 的 .apply() 方法采用函数(可调用对象)并将它们沿 DataFrame 的轴(所有行或所有列)应用。

lambda 函数将两列数据传递给 apply_tariff()

%%time
df["年齢_タイトル_apply"] = df.apply(lambda row:apply_age(row['年齢']),axis=1)

Wall time: 219 ms


数据科学必备Pandas数据处理加速技巧汇总


.apply() 的语法优势很明显,代码简洁、易读、明确。在这种情况下所用时间大约是该 .iterrows() 方法的4分之一。

.isin() 数据选择

但是如何在 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


数据科学必备Pandas数据处理加速技巧汇总


其中整个过程方法返回一个布尔序列。

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

.cut() 数据分箱

设置时间切分的列表和对那个计算的函数公式,让操作起来更简单,但是这个对于新手来说代码阅读起来有些困难。

%%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数据处理加速技巧汇总


Numpy 方法处理

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


数据科学必备Pandas数据处理加速技巧汇总


处理效率比较

对比一下上面几种不同的处理方式的效率吧。

数据科学必备Pandas数据处理加速技巧汇总


功能

运行时间(秒)

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

HDFStore 防止重新处理

通常构建复杂的数据模型时,对数据进行一些预处理会很方便。可以在这里做的一件非常有用的事情是预处理,然后以处理后的形式存储数据,以便在需要时使用。但是如何才能以正确的格式存储数据而无需再次重新处理呢?

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 条评论) “”
   
验证码:

相关文章

推荐文章