Pandas 的矢量化操作指的是在数据操作中尽量避免使用循环和显式迭代,而是利用 Pandas 提供的内置函数和方法对整个数组或数据框进行批量处理。矢量化操作充分利用了底层优化和并行处理,通常比逐行处理更高效。
矢量化操作的优势
- 性能提升:矢量化操作可以显著提高代码的运行速度,因为它们通常使用底层的 C 或 C++ 实现,并且可以利用多线程和多处理器。
- 简洁的代码:矢量化操作使代码更加简洁和易读,减少了显式的循环和条件判断。
- 减少错误:使用矢量化操作可以减少编码错误,因为操作是在整个数据集上统一执行的。
示例
普通循环操作
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'A': range(1, 6),
'B': range(10, 60, 10)
})
# 使用循环计算新列 C
df['C'] = np.nan
for i in range(len(df)):
df.loc[i, 'C'] = df.loc[i, 'A'] + df.loc[i, 'B']
print(df)
矢量化操作
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'A': range(1, 6),
'B': range(10, 60, 10)
})
# 使用矢量化操作计算新列 C
df['C'] = df['A'] + df['B']
print(df)
常用矢量化操作
- 元素级操作:如加减乘除等算术操作
df['C'] = df['A'] + df['B'] - 统计函数:如求和、均值、标准差
df['sum'] = df.sum(axis=1) - 聚合函数:如
groupby和transformdf['mean'] = df.groupby('group')['value'].transform('mean') - 条件选择:如布尔索引
df[df['A'] > 2] - 字符串操作:如
df['name'].str.lower()
例子
假设我们要对一个数据框中的数值进行归一化:
循环方式
import pandas as pd
# 创建示例数据
df = pd.DataFrame({
'A': range(1, 6),
'B': range(10, 60, 10)
})
# 使用循环归一化数据
for col in df.columns:
df[col] = (df[col] - df[col].min()) / (df[col].max() - df[col].min())
print(df)
矢量化方式
import pandas as pd
# 创建示例数据
df = pd.DataFrame({
'A': range(1, 6),
'B': range(10, 60, 10)
})
# 使用矢量化归一化数据
df = (df - df.min()) / (df.max() - df.min())
print(df)
例子中的矢量化操作
使用了 groupby 和 apply 来实现矢量化操作。通过将逻辑封装在函数中并一次性应用于整个数据框,避免了显式的循环,提高了代码效率。
改进后的代码示例
import pandas as pd
# 示例数据框
price_df = pd.DataFrame({
'itemId': ["A", "A", "A", "B", "B", "B", " C", " C", " C"],
'date': ["date1", "date2", "date3", "date4", "date5", "date6", "date7", "date8", "date9"],
'discountePrice': [1, 1, 1, 2, 3, 1, 4, 5, 5]
})
display(price_df)
# 创建 list_df
list_df = price_df.groupby("itemId")["discountePrice"].apply(set).apply(list).reset_index()
list_df.columns = ['itemId', 'list_']
display(list_df)
# 计算 price_ 和 status
def calculate_price_and_status(list_):
min_val = min(list_)
max_val = max(list_)
if len(list_) == 1:
return max_val, "1"
elif len(list_) >= 2:
if (max_val - min_val) / min_val >= 0.3:
if len(list_) == 2:
return int(min_val * 1.05), "2"
else:
list_new = [num for num in list_ if num < min_val * 1.03]
return (max(list_new) - min(list_new)) * 0.05 + min(list_new), "2+"
else:
return (max_val - min_val) * 0.05 + min_val, "2+other"
else:
return 0, "0"
# 使用矢量化方法计算 price_ 和 status
list_df[['price_', 'status']] = list_df['list_'].apply(lambda x: pd.Series(calculate_price_and_status(x)))
display(list_df)
通过使用矢量化操作,可以让代码更高效、更简洁,也更容易维护。
在这段代码中,apply(lambda x: pd.Series(calculate_price_and_status(x))) 之所以使用 pd.Series,是为了将函数 calculate_price_and_status 返回的元组转换为 pandas.Series 对象。这样,apply 方法可以将返回的 Series 拆分成单独的列。
详细解释
calculate_price_and_status 函数
def calculate_price_and_status(list_):
min_val = min(list_)
max_val = max(list_)
if len(list_) == 1:
return max_val, "1"
elif len(list_) >= 2:
if (max_val - min_val) / min_val >= 0.3:
if len(list_) == 2:
return int(min_val * 1.05), "2"
else:
list_new = [num for num in list_ if num < min_val * 1.03]
return (max(list_new) - min(list_new)) * 0.05 + min(list_new), "2+"
else:
return (max_val - min_val) * 0.05 + min_val, "2+other"
else:
return 0, "0"
这个函数返回一个元组 (price_, status)。
apply 方法
当使用 apply 方法时,希望将每个组应用 calculate_price_and_status 函数,并将返回的结果拆分到不同的列中。这时,使用 pd.Series 是非常方便的,因为它可以将元组转换为 Series 对象,并将其作为行附加到结果数据框中。
代码片段
list_df[['price_', 'status']] = list_df['list_'].apply(lambda x: pd.Series(calculate_price_and_status(x)))
list_df['list_'].apply(lambda x: ...)会对list_df['list_']列中的每个元素应用lambda函数。lambda x: pd.Series(calculate_price_and_status(x))将calculate_price_and_status函数的返回值(元组)转换为pandas.Series对象。- 由于
pd.Series(calculate_price_and_status(x))返回一个包含两个元素的Series,apply方法将其结果作为新的列添加到list_df中。 list_df[['price_', 'status']]指定了结果数据框中相应的列名称。
没有 pd.Series 的情况
如果不使用 pd.Series,apply 将返回一个包含元组的列,而不会将元组拆分成独立的列:
list_df['price_and_status'] = list_df['list_'].apply(calculate_price_and_status)
结果将是一个包含元组的单列,而不是独立的 price_ 和 status 列。