Pandas 的矢量化操作

245 阅读4分钟

Pandas 的矢量化操作指的是在数据操作中尽量避免使用循环和显式迭代,而是利用 Pandas 提供的内置函数和方法对整个数组或数据框进行批量处理。矢量化操作充分利用了底层优化和并行处理,通常比逐行处理更高效。

矢量化操作的优势

  1. 性能提升:矢量化操作可以显著提高代码的运行速度,因为它们通常使用底层的 C 或 C++ 实现,并且可以利用多线程和多处理器。
  2. 简洁的代码:矢量化操作使代码更加简洁和易读,减少了显式的循环和条件判断。
  3. 减少错误:使用矢量化操作可以减少编码错误,因为操作是在整个数据集上统一执行的。

示例

普通循环操作

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)
  • 聚合函数:如 groupbytransform df['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)

例子中的矢量化操作

使用了 groupbyapply 来实现矢量化操作。通过将逻辑封装在函数中并一次性应用于整个数据框,避免了显式的循环,提高了代码效率。

改进后的代码示例

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)))
  1. list_df['list_'].apply(lambda x: ...) 会对 list_df['list_'] 列中的每个元素应用 lambda 函数。
  2. lambda x: pd.Series(calculate_price_and_status(x))calculate_price_and_status 函数的返回值(元组)转换为 pandas.Series 对象。
  3. 由于 pd.Series(calculate_price_and_status(x)) 返回一个包含两个元素的 Seriesapply 方法将其结果作为新的列添加到 list_df 中。
  4. list_df[['price_', 'status']] 指定了结果数据框中相应的列名称。

没有 pd.Series 的情况

如果不使用 pd.Seriesapply 将返回一个包含元组的列,而不会将元组拆分成独立的列:

list_df['price_and_status'] = list_df['list_'].apply(calculate_price_and_status)

结果将是一个包含元组的单列,而不是独立的 price_status 列。

1721112470818.png