Pandas应用案例[2]

“通过将字符串列转换为 category 类型,内存占用可减少 90% 以上;使用 itertuples 替代 iterrows,遍历速度提升 6 倍;结合 Numba 的 JIT 编译,数值计算性能可媲美 C 语言。”


1. Pandas 性能

1.1. 内存优化

使用category类型可以将字符串转换为分类变量,用整数索引代替原始值,这样可以节省内存。例如:把性别这样的重复字符串转成category,内存占用大幅减少。同时,分类类型还能提高某些操作的性能,比如排序和分组,因为内部用的是整数处理,所以可以达到优化的效果。

除此之外,也可以进行数据类型优化,比如将int64转换为更小的类型如int8或者uint8。这里需要强调检查每列的数据范围,选择合适的子类型,比如:如果数值在0到255之间就用uint8。显式指定dtype是重要的,特别是在读取数据时指定类型,避免自动推断导致内存浪费。

1.1.1. Category 类型:分类数据的终极优化方案

​核心原理 - ​内存压缩:将重复的字符串(如性别、地区、产品类别)转换为整数索引,并建立映射字典。例如,将“男/女”存储为 0/1,内存占用减少 ​90%​ 以上。 - ​性能提升:分类数据在分组(groupby)、排序(sort_values)等操作中比字符串快 ​10-100 倍,因为底层使用整数运算。

使用场景 - ​低基数数据:列的唯一值数量远小于总行数(如性别仅有 2 种,但数据量百万级)。


  • ​有序分类:如评分等级(“高/中/低”)或时间段(“早/中/晚”),可指定顺序提升分析效率。

操作方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np

# 模拟金融数据:10万条交易记录
dates = pd.date_range('2025-01-01', periods=100000, freq='T')  # 分钟级交易

df = pd.DataFrame({
    'trade_type': np.random.choice(['buy', 'sell', 'cancel'], 
    size=100000),  # 交易类型
    'symbol': np.random.choice(['AAPL', 'MSFT', 'GOOGL', 'TSLA'], 
    size=100000),  # 股票代码
    'client_type': np.random.choice(['retail', 'institution', 'vip'], 
    size=100000),  # 客户类型
    'amount': np.random.uniform(1000, 1e6, size=100000)#交易金额
}, index=dates)

# 优化前内存占用
print("优化前内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

# 转换为Category类型
cat_cols = ['trade_type', 'symbol', 'client_type']
df[cat_cols] = df[cat_cols].astype('category')

# 优化后内存对比
print("优化后内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

优化前内存: 19.291857719421387 MB

优化后内存: 1.8129425048828125 MB (减少了90.6%)


Tip

定期检查内存使用情况,比如用 memory_usage 方法,来评估优化效果。

​金融场景适用字段: - ​交易类型:如 buy/sell(证券买卖)order_type(限价单/市价单) - ​资产类别:如 stockbondETF - ​客户等级:如 VIP普通机构 - 地域分类:如 CNUSHK(交易市场归属)

当列的唯一值较少且重复较多时,使用category效果最好。例如性别、地区代码等。如果分类变量的类别数量远小于总行数,转换后的内存节省会更明显。注意category类型不适合频繁变更类别的情况,这可能增加计算开销。另外,使用pd.Categorical或者cut函数创建分类数据需要注意处理缺失值的问题,因为category类型不支持NaN,所以在转换前需要处理缺失值。

1.1.2. 紧凑数据类型:精准狙击内存浪费

​数值类型优化 - ​整数类型:根据数值范围选择最小子类型:

1
2
# 检查范围后转换
df['age'] = df['age'].astype('uint8')  # 0-255 范围
- 浮点类型:优先使用 float32(精度足够时),内存减少 ​50%​ .


​布尔类型优化 将仅有 True/False 的列转换为 bool 类型:

1
    df['is_active'] = df['is_active'].astype('bool')

​时间类型优化 使用 datetime64[ns] 而非 object 存储日期,内存减少 ​75%​ 且支持时间序列运算。

金融数据常包含以下高优化价值字段: - ​离散型分类字段:交易类型(buy/sell)、证券代码(AAPL/TSLA)、客户等级(VIP/普通) - ​数值型字段:交易金额(float64)、持仓量(int64)、时间戳(object) - ​状态标识字段:是否盘后交易(True/False)、风险标记(high/medium/low)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
import numpy as np

# 生成10万条模拟交易数据
dates = pd.date_range('2025-01-01', periods=100000, freq='T')  # 分钟级时间戳
df = pd.DataFrame({
    'trade_type': np.random.choice(['buy', 'sell', 'cancel'], size=100000),
    'symbol': np.random.choice(['AAPL', 'MSFT', 'GOOGL', 'TSLA'], size=100000),
    'client_level': np.random.choice(['VIP', '普通', '机构'], size=100000),
    'amount': np.random.uniform(1000, 1e6, size=100000),
    'position': np.random.randint(1, 10000, size=100000)
}, index=dates)

print("优化前内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 转换分类类型
cat_cols = ['trade_type', 'symbol', 'client_level']
df[cat_cols] = df[cat_cols].astype('category')

# 查看内存优化效果
print("优化后内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

# 压缩数值类型
df['amount'] = df['amount'].astype('float32')  # 金额压缩为32位浮点
df['position'] = df['position'].astype('int16')  # 持仓量压缩为16位整数

# 时间戳优化(假设原始数据为字符串)
df['trade_time'] = pd.to_datetime(df.index)  # 转为datetime64[ns]

# 最终内存对比
print("最终内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

优化前内存: 21.358366012573242 MB

优化后内存: 2.575934410095215 MB

最终内存: 2.385199546813965 MB

1.1.3. 高频交易场景综合优化
  1. ​分块读取+类型预定义
    1
    2
    3
    4
    5
    # 读取1GB级交易日志时预定义类型
    dtypes = {
        'symbol': 'category',
        'amount': 'float32',
        'position': 'int16',
    

1
2
3
4
5
6
'trade_type': 'category'
}
chunks = pd.read_csv('trade_log.csv', chunksize=100000, dtype=dtypes)
processed_chunks = [chunk.groupby('symbol')['amount'].sum() 
for chunk in chunks]
final_result = pd.concat(processed_chunks)
  1. ​分组统计加速
    1
    2
    3
    # 按证券代码统计交易量(提速5倍)
    df['symbol'] = df['symbol'].cat.add_categories(['UNKNOWN'])  # 处理新增代码
    trade_volume = df.groupby('symbol', observed=True)['position'].sum()
    
1.1.4. 进阶技巧
  1. ​有序分类(风险等级分析)​

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    from pandas.api.types import CategoricalDtype
    
    # 定义有序风险等级[5](@ref)
    risk_order = CategoricalDtype(
        categories=['low', 'medium', 'high'], 
        ordered=True
    )
    df['risk_level'] = df['risk_level'].astype(risk_order)
    
    # 筛选高风险交易(提速10倍)
       high_risk_trades = df[df['risk_level'] > 'medium']
    

  2. 布尔类型压缩(盘后交易标记)​


1
2
3
4
# 生成盘后交易标记(内存减少87%)[4](@ref)
df['is_after_hours'] = df['trade_time'].apply(
    lambda x: x.hour < 9 or x.hour > 16
).astype('bool')

Warning

  • ​动态类别管理:新增证券代码时需调用 df['symbol'].cat.add_categories(['NVDA'])
  • 数值溢出风险:持仓量若超过 int16 范围(-32768~32767),需改用 int32
  • 时间序列分析:datetime 类型支持高效时间窗口计算(如 .rolling('30T'))

通过上述方法,可在高频交易分析、客户行为画像等场景中实现 ​内存减少80%+分组操作提速5-10倍 的显著优化效果。对于超大规模数据集(如10亿级交易记录),建议结合 Dask 或 Modin 实现分布式计算。

1.2. 优化迭代

使用 itertuples 而不是 iterrows, 使用 apply 来优化迭代,先筛选再计算。itertuples 比 iterrows 快很多,因为 itertuples 返回的是命名元组,而 iterrows 返回的是 Series 对象,这会慢很多。有案例表示使用 iterrows 处理 600 万行数据需要 335 秒,而 itertuples 只需要 41 秒,快了近 6 倍。

1.2.1. 迭代方式性能对比与优化原理
  1. ​itertuples 与 iterrows 性能差异

方法 数据结构 百万行耗时 适用场景 核心优势
​iterrows 生成 (index, Series) 对 85.938s 需要行索引的简单遍历 直观易用
​ itertuples 生成命名元组 7.656s 大规模数据遍历 内存占用减少50%,速度提升6倍
​ apply 向量化函数应用 0.03s 条件逻辑较复杂的行级计算 语法简洁,自动类型优化

Notes

  • iterrows 每次迭代生成 Series 对象,触发内存分配和类型检查(面向对象开销)
  • itertuples 返回轻量级 namedtuple,直接通过属性访问数据(C语言层级优化)
  1. ​apply 函数的优化机制
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # 示例:计算股票交易费用(佣金率分档)
    def calc_fee(row):
        if row['volume'] > 10000:
            return row['amount'] * 0.0002
        elif row['volume'] > 5000:
            return row['amount'] * 0.0003
        else:
            return row['amount'] * 0.0005
    
    # 优化点:使用 axis=1 按行应用
    df['fee'] = df.apply(calc_fee, axis=1)  # 比循环快3倍
    
1.2.2. 金融数据综合优化案例
  1. 生成模拟高频交易数据
    1
    2
    3
    # 生成100万条股票交易记录(含时间戳、代码、价格、成交量)
    dates = pd.date_range('2025-03-28 09:30', periods=1_000_000, freq='S')
    symbols = ['AAPL', 'MSFT', 'GOOG', 'AMZN', 'TSLA']
    

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
df = pd.DataFrame({
    'symbol': np.random.choice(symbols, 1_000_000),
    'price': np.random.uniform(50, 500, 1_000_000).round(2),
    'volume': np.random.randint(100, 50_000, 1_000_000),
    'trade_type': np.random.choice(['buy', 'sell'], 1_000_000)
}, index=dates)

print("优化前内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

# 内存优化:分类列转换
df['symbol'] = df['symbol'].astype('category')  # 内存减少85%
df['trade_type'] = df['trade_type'].astype('category')

print("优化后内存:", df.memory_usage(deep=True).sum() / 1024**2, "MB")

优化前内存: 138.75994682312012 MB

优化后内存: 24.796205520629883 MB

  1. itertuples 实战:计算交易金额
    1
    2
    3
    4
    5
    6
    7
    8
    # 传统 iterrows 写法(避免使用!)
    import time
    t1 = time.time()
    total_amount = 0
    for idx, row in df.iterrows():  # 预估耗时85秒
        total_amount += row['price'] * row['volume']
    t2 = time.time()
    print("传统 iterrows 写法:",t2-t1,"s")
    

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 优化后 itertuples 写法
total_amount = 0
for row in df.itertuples():  # 耗时约7秒
    total_amount += row.price * row.volume
t3 = time.time()
print("优化后 itertuples 写法:",t3-t2,"s")

# 终极优化:向量化计算(推荐!)
df['amount'] = df['price'] * df['volume']  # 耗时0.03秒
t4 = time.time()
print("终极优化:向量化计算:",t4-t3,"s")

传统 iterrows 写法: 85.93825674057007 s

优化后 itertuples 写法: 7.655602216720581 s

终极优化:向量化计算: 0.032360076904296875 s

  1. ​apply 实战:计算波动率因子
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    def volatility_factor(row):  # 定义波动率计算函数
        if row['volume'] > 20000:
            return row['price'] * 0.015
        elif (row['volume'] > 10000) & (row['trade_type'] == 'buy'):
            return row['price'] * 0.010
        else:
            return row['price'] * 0.005
    # 应用优化
    t5 = time.time()
    df['vol_factor'] = df.apply(volatility_factor, axis=1)  # 耗时约3秒
    t6 = time.time()
    print("定义波动率计算函数:",t6-t5,"s")
    

定义波动率计算函数: 24.482948064804077 s


  1. 先筛选再计算策略
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    # 非交易时段数据过滤(先筛选)
    market_hours = df.between_time('09:30', '16:00')  # 减少30%数据量
    
    # 仅处理大额交易(金额>100万)
    large_trades = market_hours[market_hours['amount'] > 1_000_000]
    
    # 分块处理(内存优化)
    t7 = time.time()
    chunks = (large_trades.groupby('symbol')
                        .apply(lambda x: x['amount'].mean())
                        .reset_index(name='avg_large_trade'))
    t8 = time.time()
    
    print("先筛选再计算策略:",t8-t7,"s")
    

先筛选再计算策略: 0.044037818908691406 s

apply可以利用内部优化,比循环更快,但不如矢量化操作。

1.2.3. 性能对比与最佳实践

Tip

最佳实践优先级: ​1. 向量化运算 > 2. ​itertuples > 3. ​apply > 4. ​iterrows - 优先使用 df['col'] = df['col1'] * df['col2'] 形式 - 复杂逻辑用 np.where() 或 pd.cut() 替代循环

1.2.4. 注意事项

  1. 数据预处理

    • 将时间戳设为索引 df.set_index('timestamp', inplace=True)
    • 数值列转换为最小类型: df['volume'] = df['volume'].astype('int32')
  2. 避免链式索引

1
2
3
4
5
# 错误写法(触发警告)
df[df['symbol'] == 'AAPL']['price'] = 200  

# 正确写法
df.loc[df['symbol'] == 'AAPL', 'price'] = 200  # 效率提升30%
  1. ​内存管理
    • 分块读取: pd.read_csv('trades.csv', chunksize=100000)
    • 及时删除中间变量: del temp_df 释放内存

完整代码示例可通过 Jupyter Notebook 运行测试,建议使用金融高频交易数据集(如TAQ数据)验证优化效果。对于超大规模数据(>1亿行),推荐结合 Dask 或 Modin 实现分布式计算。

1.3. 使用numpy和numba

1.3.1. Numba核心原理与优势

Numba 是 Python 的即时(JIT)编译器,通过将 Python 函数编译为机器码,显著提升计算效率,尤其适合数值计算和 Numpy 数组操作。


  • ​即时编译:通过 @jit 装饰器自动优化函数,消除 Python 解释器开销。
  • ​并行加速:使用 parallel=True 和 prange 实现多线程并行计算。
  • GPU支持:通过 @cuda.jit 将计算任务卸载到 GPU,适用于超大规模数据处理。
1.3.2. 金融数据处理优化案例
  1. ​计算股票收益率波动率(Numba加速)​
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import numpy as np
    from numba import jit
    
    # 生成金融数据:100万条股票价格序列
    np.random.seed(42)
    prices = np.random.normal(100, 5, 1_000_000).cumsum()
    
    # 传统Python实现
    def calc_volatility(prices):
        returns = np.zeros(len(prices)-1)
        for i in range(len(prices)-1):
            returns[i] = (prices[i+1] - prices[i]) / prices[i]
        return np.std(returns) * np.sqrt(252)
    
    # Numba优化实现
    @jit(nopython=True)
    def calc_volatility_numba(prices):
        returns = np.zeros(len(prices)-1)
        for i in range(len(prices)-1):
            returns[i] = (prices[i+1] - prices[i]) / prices[i]
        return np.std(returns) * np.sqrt(252)
    
    # 性能对比
    %timeit calc_volatility(prices)    # 约 920 ms
    %timeit calc_volatility_numba(prices)  # 约 7.3 ms
    

921 ms ± 87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

7.27 ms ± 183 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)

  1. 蒙特卡洛期权定价(并行计算)​
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    from numba import njit, prange
    
    @njit(parallel=True)
    def monte_carlo_pricing(S0, K, r, sigma, T, n_simulations):
        payoffs = np.zeros(n_simulations)
        for i in prange(n_simulations):
            ST = S0 * np.exp((r - 0.5*sigma**2)*T + sigma*np.sqrt(T)*np.random.normal())
            payoffs[i] = max(ST - K, 0)
        return np.exp(-r*T) * np.mean(payoffs)
    
    # 参数设置
    params = (100, 105, 0.05, 0.2, 1, 1_000_000)
    result = monte_carlo_pricing(*params)  # 约 320 ms(比纯Python快35倍)
    
1.3.3. 关键优化策略
  1. 数据类型特化 强制指定输入类型避免动态检查:
    1
    2
    3
    @jit(nopython=True, fastmath=True)
    def vec_dot(a: np.ndarray, b: np.ndarray) -> float:
        return np.dot(a, b)
    

  1. 内存预分配

    1
    2
    3
    4
    5
    6
    @jit(nopython=True)
    def moving_average(data, window):
        ma = np.empty(len(data) - window + 1)
        for i in range(len(ma)):
            ma[i] = np.mean(data[i:i+window])
        return ma
    

  2. ​避免Python对象 在 Numba 函数中禁用 Python 对象(nopython=True),确保全程机器码执行。

Note

最佳实践 - 优先使用 @njit(等价于 @jit(nopython=True)) - 对大循环使用 prange 替代 range 实现并行 - 对 np.ufunc 函数进行二次加速(如 np.sqrt、np.exp) - 避免在 JIT 函数中混合使用 Python 原生类型与 Numpy 类型

1.3.4. 扩展应用
  1. 与Pandas结合
    1
    2
    3
    @jit
    def pandas_apply_optimized(df: pd.DataFrame):
        return df['price'].values * df['volume'].values  # 直接操作Numpy数组
    

  1. GPU加速(CUDA)​
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    from numba import cuda
    
    @cuda.jit
    def cuda_matmul(A, B, C):
        i, j = cuda.grid(2)
        if i < C.shape[0] and j < C.shape[1]:
            tmp = 0.0
            for k in range(A.shape[1]):
                tmp += A[i, k] * B[k, j]
            C[i, j] = tmp
    

Tip

注意事项: - ​编译开销:首次运行 JIT 函数会有编译耗时,后续调用直接使用缓存 - 调试限制:Numba 函数不支持 pdb 断点调试,需通过 print 输出中间值 - 兼容性:部分 Numpy 高级功能(如 np.linalg.svd)在 Numba 中受限

通过合理运用 Numpy 的向量化操作与 Numba 的 JIT 编译,可在金融量化分析、高频交易等场景实现 ​C 语言级性能,同时保持 Python 的开发效率。建议结合 %%timeit 和 Numba 的 cache=True 参数持续优化热点代码。


Pandas应用案例[3]

“Modin 通过多核并行加速 Pandas 操作,读取 10GB CSV 文件比 Pandas 快 4-8 倍;Polars 基于 Rust 架构,内存占用仅为 Pandas 的 1/3;Dask 则支持分布式计算,轻松处理 TB 级数据。”


1.4. 使用eval或者query

关于 query 方法,我它类似于SQL的where子句,允许用字符串表达式,这样代码更简洁。比如df.query('Q1 > Q2 > 90'),还支持用@符号引入外部变量。比如计算平均分后筛选高于平均分的数据。同时,eval方法类似,但返回布尔索引,需要配合df[]使用,例如:df[df.eval("Q1 > 90 > Q3 >10")]

isin 方法,用于筛选某列的值是否在指定列表中。例如,用 b1["类别"].isin(["能源","电器"]) 来筛选类别列中的值。此外,还可以结合多个条件,例如:df[df['ucity'].isin(['广州市','深圳'])]

1.4.1. query() 函数:SQL风格的条件筛选
  1. 核心语法

    1
    df.query('表达式')  # 表达式需用引号包裹,支持逻辑运算符和列名直接引用
    

  2. 金融场景示例

    1
    2
    3
    4
    5
    6
    7
    """案例1:筛选特定股票代码的高额交易"""
    # 筛选AAPL或TSLA股票,且金额超过100万的交易
    df.query("symbol in ['AAPL', 'TSLA'] and amount > 1e6")
    
    """案例2:动态引用外部变量"""
    avg_amount = df['amount'].mean()  # 计算平均交易金额
    df.query("amount > @avg_amount * 2")  # 筛选金额超过平均2倍的交易[3,5](@ref)
    


1
2
3
"""案例3:多条件组合"""
# 筛选2025年Q1买入且成交价高于开盘价的交易
df.query("trade_type == 'buy' and trade_date >= '2025-01-01' and price > open_price")
  1. 性能优势
  2. ​表达式优化:底层通过 numexpr 库加速计算,比传统布尔索引快30%以上
  3. ​列名处理:列名含空格或特殊字符时需用反引号包裹(如 收盘价 > 100)
1.4.2. eval() 函数:表达式生成布尔索引
  1. 核心语法

    1
    2
    mask = df.eval("表达式")  # 返回布尔数组
    df[mask]  # 用布尔索引筛选数据
    

  2. ​金融场景示例

    1
    2
    3
    4
    5
    6
    7
    8
    """案例1:计算复杂交易条件"""
    # 筛选波动率超过阈值且交易量增长的股票
    df[df.eval("(high - low)/close > 0.05 and volume > volume.shift(1)")]
    
    """案例2:动态公式计算"""
    # 筛选夏普比率高于行业平均的基金
    industry_avg = 1.2
    df[df.eval("(returns - risk_free_rate)/std_dev > @industry_avg")]
    

  3. ​与query()的区别

  4. eval() 返回布尔数组,需配合 df[] 使用;query() 直接返回筛选后的DataFrame
  5. 两者共享相同表达式引擎,性能差异可忽略,按代码简洁性选择即可

1.4.3. isin() 函数:多值匹配筛选
  1. 核心语法

    1
    df[df['列名'].isin(值列表)]  # 筛选列值存在于列表中的行
    

  2. ​金融场景示例

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    """案例1:筛选特定股票池"""
    blue_chips = ['600519.SH', '000858.SZ', '601318.SH']  # 上证50成分股
    df[df['symbol'].isin(blue_chips)]
    
    """案例2:排除ST/ST风险股"""
    risk_stocks = ['*ST长生', 'ST康美']  
    df[~df['stock_name'].isin(risk_stocks)]  # 使用~取反[2](@ref)
    
    """案例3:联合多列筛选"""
    # 筛选沪深300且行业为科技或金融的股票
    target_industries = ['Technology', 'Financials']
    df[df['index'].isin(['000300.SH']) & df['industry'].isin(target_industries)]
    

  3. 进阶用法

  4. ​字典筛选:多列联合匹配(如 df[df.isin({'symbol':'AAPL', 'exchange':'NASDAQ'})])
  5. 性能优化:对大列表(>1万元素)建议先转换为集合(set())提升速度
1.4.4. 综合性能优化策略
  1. 先筛选再计算

1
2
3
4
5
6
7
# 错误:先计算全量再筛选
df['return'] = df['close'].pct_change()  
df_filtered = df[df['volume'] > 1e6]

# 正确:先筛选减少计算量
df_filtered = df[df['volume'] > 1e6].copy()  
df_filtered['return'] = df_filtered['close'].pct_change()[6](@ref)
  1. ​避免链式操作

    1
    2
    3
    4
    # 错误:两次索引降低性能
    df[df['symbol'] == 'AAPL']['close']  
    # 正确:单次loc操作
    df.loc[df['symbol'] == 'AAPL', 'close'][3](@ref)
    

  2. ​类型优化

    1
    2
    # 将字符串列转为category提升isin速度
    df['symbol'] = df['symbol'].astype('category')[8](@ref)
    

1.4.5. 方法对比与适用场景
方法 适用场景 性能优势
query() 复杂多条件组合,需动态变量引用 表达式优化加速
eval() 生成中间布尔索引,用于后续处理 与query性能接近
isin() 快速匹配离散值列表(如股票代码) 集合加速+类型优化

实践建议: - ​高频筛选:优先用 query() 保持代码简洁


  • ​超大列表:用 isin() + 集合类型提升速度
  • ​动态计算:eval() 适合嵌入数学公式或跨列运算

1.5. Pandas 的其它替代方案

1.5.1. modin:单机多核并行加速器

一行代码,实现pandas替代,并拥有多核、不受内存限制的计算能力。

  1. ​核心原理
  2. ​并行化改造:将 Pandas 的 DataFrame 拆分为多个分区,利用多核 CPU 并行处理,底层支持 Ray 或 Dask 引擎。
  3. ​语法兼容性:仅需修改导入语句(import modin.pandas as pd),即可无缝替代原生 Pandas,支持 90% 以上常用 API。

  4. ​性能优势

  5. 读取加速:读取 10GB CSV 文件时,比 Pandas 快 4-8 倍。
  6. ​计算优化:groupby 等聚合操作在 4 核机器上提速 3-5 倍,内存占用减少 30%。
  7. ​适用场景:单机环境下处理 100MB~50GB 数据集,适合金融高频交易日志分析、用户行为数据清洗等。

  8. ​使用案例

    1
    2
    3
    # 读取大规模交易数据(并行加速)
    import modin.pandas as pd
    df = pd.read_csv("trades.csv", parse_dates=["timestamp"])
    


1
2
# 实时计算每分钟交易量
volume_by_minute = df.groupby(pd.Grouper(key="timestamp", freq="T"))["amount"].sum().compute()
  1. ​注意事项
  2. ​小数据集劣势:处理 <100MB 数据时可能比 Pandas 更慢(启动开销)。
  3. ​内存消耗:需预留 2-3 倍数据大小的内存,避免 OOM。
1.5.2. polars:Rust 驱动的极速引擎

最快的tableu解决方案

  1. ​核心原理
  2. ​Rust + Arrow 架构:基于 Rust 语言和 Apache Arrow 内存格式,支持零拷贝数据处理与 SIMD 指令优化。
  3. ​多线程与惰性执行:自动并行化计算,通过 lazy() 延迟执行并优化查询计划。

  4. ​性能优势

  5. 速度对比:同等操作比 Pandas 快 5-10 倍,1 亿行 groupby 计算仅需 11 秒(Pandas 需 187 秒)。
  6. ​内存效率:内存占用仅为 Pandas 的 1/3,支持处理内存不足时的核外计算。

  7. ​适用场景

  8. ​高频金融数据:如实时波动率计算、订单簿快照分析。
  9. ​复杂聚合:多条件统计、时间窗口滚动计算(如 VWAP)。

  10. 代码示例


1
2
3
4
5
6
7
8
9
import polars as pl
# 惰性执行优化查询
df = pl.scan_csv("market_data.csv")
result = (
   df.filter(pl.col("price") > 100)
   .groupby("symbol")
   .agg([pl.col("volume").sum(), pl.col("price").mean()])
   .collect()  # 触发计算
)

Tip

​注意事项 - ​语法差异:部分 Pandas 方法需改写(如 df[df.col > 0] → df.filter(pl.col("col") > 0))。 - ​可视化兼容性:需转换为 Pandas 或 NumPy 才能使用 Matplotlib/seaborn。

1.5.3. dask:分布式计算的瑞士军刀

分布式tableu,可运行在数千结点上

  1. ​核心原理
  2. ​分布式任务调度:将任务拆分为 DAG(有向无环图),支持单机多核或集群分布式执行。
  3. ​核外计算:通过分区处理超出内存的数据集(如 TB 级日志)。

  1. ​性能优势
  2. ​横向扩展:在 16 核机器上处理 50GB 数据比 Pandas 快 10 倍,支持扩展到千节点集群。
  3. ​兼容生态:无缝对接 XGBoost、Dask-ML 等库,支持分布式模型训练。

  4. ​适用场景

  5. ​超大规模数据:如全市场历史行情分析、社交网络图谱计算。
  6. ​ETL 流水线:多步骤数据清洗与特征工程(需依赖管理)。

  7. 实战技巧

    1
    2
    3
    4
    5
    import dask.dataframe as dd
    # 分块读取与处理
    ddf = dd.read_csv("s3://bucket/large_file_*.csv", blocksize="256MB")
    # 并行计算每支股票的年化波动率
    volatility = ddf.groupby("symbol")["return"].std().compute()
    

Tip

​注意事项 - ​调试复杂性:需用 Dask Dashboard 监控任务状态,定位数据倾斜问题。 - ​配置优化:合理设置分区大小(建议 100MB~1GB),避免调度开销。


1.5.4. 选型决策树
场景 ​推荐工具 ​理由
单机中数据(<50GB) Modin 零代码修改,快速提升现有 Pandas 脚本性能
高频计算/内存受限 Polars 极致速度与低内存消耗,适合量化交易场景
分布式/超大数据(>1TB) Dask 支持集群扩展,生态完善

​注:实际测试显示,Polars 在单机性能上全面领先,而 Dask 在分布式场景下更具优势。建议结合数据规模与硬件资源综合选择。

Pandas应用案例[1]

“Alphalens 要求因子数据是双重索引的 Series,价格数据是日期为索引、资产代码为列的 DataFrame。通过 Pandas 的 pivot_table 和 set_index,可以轻松完成格式转换,为因子分析奠定基础。”


1. 通过rolling方法实现通达信例程

通达信 是一款由中国深圳市财富趋势科技股份有限公司开发的金融投资软件,主要用于股票、期货等金融市场的行情分析、技术研究和交易执行。通达信在国内券商中的覆盖率超过80%,服务包括招商证券、广发证券等头部机构。支持超5000万投资者,峰值并发用户达800万,以界面简洁、行情更新快著称。适用于个人投资者、专业交易员及量化分析,尤其适合需要快速响应行情和技术分析的场景。

rolling 是 pandas 库中用于执行滚动窗口计算的核心方法,适用于时间序列或数据框的滑动统计分析。以下是其核心要点: - 功能:对数据按固定窗口大小(如时间周期或观测值数量)滑动,并在每个窗口内执行聚合或自定义计算(如均值、极值等) - 典型应用:移动平均、波动率计算(标准差)、技术指标(如MACD)等。

核心参数: | 参数 | 说明 | |-------|-------------| | window | 窗口大小(整数或时间偏移,如 '5D') | | min_periods | 窗口内最少有效数据量,否则结果为 NaN(默认等于 window) | | center | 窗口对齐方式(False为右对齐,True为居中) | | win_type | 窗口权重类型(如 'gaussian') |

1.1. HHV(N周期内最高值)


通达信定义:HHV(X, N) 表示在最近 N 个周期内序列 X 的最高值。可以通过 pandas 实现:

1
2
def HHV(s: pd.Series, n: int) -> pd.Series:
    return s.rolling(n).max()

1.2. LLV(N周期内最低值)

通达信定义:LLV(X, N) 表示在最近 N 个周期内序列 X 的最低值。

1
2
def LLV(s: pd.Series, n: int) -> pd.Series:
    return s.rolling(n).min()

1.3. HHVBARS(最高值到当前周期的距离)​

通达信定义:HHVBARS(X, N) 表示最近 N 个周期内,最高值所在位置到当前周期的距离(周期数)。

1
2
3
4
def HHVBARS(s: pd.Series, n: int) -> pd.Series:
    def _find_idx(x):
        return len(x) - np.argmax(x[::-1]) - 1 if not np.isnan(x).all() else np.nan
    return s.rolling(n).apply(_find_idx, raw=True)


1.4. LAST(条件连续满足的周期数)​

通达信定义:LAST(X, A, B) 表示过去 B 个周期中,有至少 A 个周期满足条件 X。

1
2
def LAST(condition: pd.Series, a: int, b: int) -> pd.Series:
    return condition.rolling(b).sum() >= a

1.5. 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np

# 导入数据
start = datetime.date(2023, 1, 1)
end = datetime.date(2023, 12, 31)
df = load_bars(start, end)
df.tail()

# 应用函数
df['HHV_5'] = HHV(df['high'], 5)       # 5日最高价
df['LLV_5'] = LLV(df['low'], 5)        # 5日最低价
df['HHVBARS_5'] = HHVBARS(df['high'], 5)  # 最高价距离当前的天数
df['LAST_UP_3_5'] = LAST(df['close'] > df['close'].shift(1), 3, 5)  # 5日内至少3日上涨

print(df)

2. 补齐分钟线缺失的复权因子

量化分析中,可能在处理股票分钟线数据时,复权因子数据存在缺失,需要根据时间进行临近匹配,确保每个分钟数据点都有正确的复权因子。复权因子通常是在股票发生拆分或分红时调整的,这些事件的时间点可能不会正好匹配分钟线的每个时间戳,例如: - 复权因子生效时间:2025-03-27 10:30:00(事件触发时刻) - 分钟线时间戳:2025-03-27 10:30:01、10:30:02(交易数据)

Tip

传统 merge 或 join 方法无法匹配此类时间邻近但非严格相等的数据,需用 ​as-of join 功能解决。使用 merge_asof 可以找到每个分钟线时间点之前最近的复权因子,确保正确应用调整。

merge_asof 是 Pandas >=0.19.0 引入的时间导向非精确匹配函数,专为此类场景设计。

2.1. 基础语法和示例

2.1.1. 基础语法

1
2
3
4
5
6
7
8
pd.merge_asof(
    left,          # 左表(分钟线数据)
    right,         # 右表(复权因子数据)
    on='time',     # 时间列名(必须排序)
    direction='backward',  # 匹配方向:向前/向后/最近
    tolerance=pd.Timedelta('1min'),  # 最大时间差
    allow_exact_matches=True  # 是否允许精确匹配
)
2.1.2. 基础示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import numpy as np

# 生成示例数据(假设复权因子在非整分钟时间点更新)
minute_data = {
    'time': [
        '2025-03-27 10:29:58',  # 完整日期+时间
        '2025-03-27 10:30:01', 
        '2025-03-27 10:30:03', 
        '2025-03-27 10:30:05', 
        '2025-03-27 10:30:08'  # 确保所有时间包含日期
    ],
    'price': [100.2, 101.5, 102.0, 101.8, 103.2]
}
df_trade = pd.DataFrame(minute_data).sort_values('time')
adjust_data = {
    'time': [
        '2025-03-27 10:29:55', 
        '2025-03-27 10:30:00', 
        '2025-03-27 10:30:06'
    ],

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    'adjust_factor': [1.0, 0.95, 1.02]
}
df_adjust = pd.DataFrame(adjust_data).sort_values('time')
# 强制转换为 datetime 类型(处理混合格式)
df_trade['time'] = pd.to_datetime(
    df_trade['time'], 
    format='%Y-%m-%d %H:%M:%S', 
    errors='coerce'
)
df_adjust['time'] = pd.to_datetime(
    df_adjust['time'], 
    format='%Y-%m-%d %H:%M:%S', 
    errors='coerce'
)
assert df_trade['time'].dtype == 'datetime64[ns]', "交易数据时间列转换失败"
assert df_adjust['time'].dtype == 'datetime64[ns]', "复权因子时间列转换失败"
# 关键步骤:按时间向前匹配最近的复权因子
merged = pd.merge_asof(
    df_trade,
    df_adjust,
    on='time',
    direction='backward',  # 取<=当前时间的最近值
    tolerance=pd.Timedelta(minutes=1)  # 最多允许1分钟间隔
)
merged

50%


2.2. 进阶技巧

2.2.1. 多标的匹配(股票代码分组)
1
2
3
4
5
6
7
8
# 假设数据包含多只股票
merged = pd.merge_asof(
    df_trade.sort_values('time'),
    df_adjust.sort_values('time'),
    on='time',
    by='symbol',  # 按股票代码分组匹配
    direction='backward'
)
2.2.2. 动态调整因子生效时间

若复权因子生效时间需要提前或延后,可预处理右表时间:

1
2
df_adjust['time'] = df_adjust['time'] + pd.Timedelta(seconds=30)  # 延后30秒生效
df_adjust

50%


2.2.3. 处理缺失值
1
merged['adjust_factor'] = merged['adjust_factor'].ffill()  # 前向填充缺失值

2.3. 与其他方法对比

方法 适用场景 优点 缺点
merge_asof 时间邻近匹配 处理非对齐时间戳效率高 需预先排序数据
merge 精确时间匹配 结果精确 无法处理时间偏差
concat 简单堆叠 快速合并 不处理时间关联

3. 为Alphalens准备数据

在使用Alphalens进行因子分析时,我们往往需要将因子数据和价格数据整理成特定的格式。Alphalens要求因子数据是一个具有双重索引(日期和资产)的Series,而价格数据是DataFrame,行是日期,列是资产,值是价格。这一点非常重要,如果格式不对,Alphalens会报错。

因此,我们需要知道如何从原始数据转换到这种格式。这里我们考虑使用pivot_table来转换价格数据,以及使用set_index来创建因子数据的双重索引。

3.1. 数据格式规范(Alphalens 强制要求)


3.1.1. 因子数据格式

需构建双重索引的 Series,索引顺序为:日期 -> 资产代码,值为因子数值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 原始数据示例(含日期、股票代码、因子值)
raw_factor = pd.DataFrame({
    'date': ['2025-03-25', '2025-03-25', '2025-03-26', '2025-03-26'],
    'symbol': ['AAPL', 'MSFT', 'AAPL', 'MSFT'],
    'value': [0.5, -0.3, 0.7, 0.2]
})

# 转换为Alphalens格式
factor = raw_factor.set_index(['date', 'symbol'])['value']
factor.index = pd.MultiIndex.from_arrays(
    # 确保日期为datetime类型
    [pd.to_datetime(factor.index.get_level_values('date')),  
     factor.index.get_level_values('symbol')]
)
factor

50%

3.1.2. ​价格数据格式

需构建 ​日期为索引、资产代码为列名 的 DataFrame:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 原始数据示例(含日期、股票代码、收盘价)
raw_price = pd.DataFrame({
    'date': ['2025-03-25', '2025-03-25', '2025-03-26', '2025-03-26'],
    'symbol': ['AAPL', 'MSFT', 'AAPL', 'MSFT'],
    'close': [150, 280, 152, 285]
})

# 转换为Alphalens格式
prices = raw_price.pivot(index='date', columns='symbol', values='close')
prices.index = pd.to_datetime(prices.index)  # 日期转换为datetime类型

50%

3.2. 关键预处理操作

3.2.1. 时间索引对齐
1
2
3
4
# 检查时间范围是否重叠
print("因子时间范围:",factor.index.get_level_values('date').min()
      , "~", factor.index.get_level_values('date').max())
print("价格时间范围:",prices.index.min(), "~", prices.index.max())

1
2
# 若存在时间缺口,用前向填充(避免未来数据)
prices = prices.ffill()
3.2.2. 异常值处理
1
2
3
4
5
6
7
8
# Winsorize去极值(保留98%数据)
factor_clipped = factor.clip(
    lower=factor.quantile(0.01),
    upper=factor.quantile(0.99)
)

# 标准化处理(Z-score)
factor_normalized = (factor - factor.mean()) / factor.std()
3.2.3. 缺失值处理
1
2
3
4
5
6
# 删除缺失值超过50%的资产
valid_symbols = factor.unstack().isnull().mean() < 0.5
factor = factor.loc[:, valid_symbols[valid_symbols].index.tolist()]

# 前向填充剩余缺失值
factor = factor.groupby(level='symbol').ffill()

3.3. 高级操作技巧

3.3.1. 多因子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 假设存在动量因子和市值因子
factor_mom = ...  # 动量因子数据
factor_size = ... # 市值因子数据

# 横向拼接为MultiIndex列
combined = pd.concat(
    [factor_mom.rename('momentum'), factor_size.rename('size')],
    axis=1
)

# 转换为双层索引
combined = combined.stack().swaplevel(0, 1).sort_index()
3.3.2. ​行业中性化处理
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 假设有行业分类数据
industries = pd.Series({
    'AAPL': 'Technology',
    'MSFT': 'Technology',
    'XOM': 'Energy'
}, name='industry')

# 按行业分组标准化
factor_neutral = factor.groupby(
    industries, group_keys=False
).apply(lambda x: (x - x.mean()) / x.std())

3.4. 数据验证与接口对接

3.4.1. 格式验证

1
2
3
4
5
# 检查因子索引层级
assert factor.index.names == ['date', 'symbol'], "因子索引命名错误"

# 检查价格数据类型
assert prices.columns.dtype == 'object', "价格数据列名应为资产代码"
3.4.2. Alphalens 接口调用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from alphalens.utils import get_clean_factor_and_forward_returns

# 生成分析数据集
factor_data = get_clean_factor_and_forward_returns(
    factor=factor,
    prices=prices,
    periods=(1, 5, 10),  # 1/5/10日收益率
    quantiles=5,         # 分为5组
    filter_zscore=3      # 剔除3倍标准差外的异常值
)

# 生成完整分析报告
import alphalens
alphalens.tears.create_full_tear_sheet(factor_data)

3.5. 常见问题解决方案

问题现象 解决方法
ValueError: 价格数据包含未来信息 检查价格数据时间戳是否晚于因子时间戳,用prices = prices.shift(1) 滞后一期
KeyError: 资产代码不匹配 使用prices.columns.intersection(factor.index.get_level_values('symbol')) 取交集
图表显示空白 在Jupyter Notebook中运行,并添加%matplotlib inline 魔术命令

通过上述操作,可高效完成从原始数据到Alphalens兼容格式的转换,确保因子分析的准确性。实际应用中建议先在小样本数据上测试,再扩展至全量数据。

Pandas核心语法[7]

“Pandas 的 DataFrame 提供了强大的样式功能,可以通过 Styler 对象实现类似 Excel 的条件着色效果。此外,Pandas 内置的绘图方法支持多种图表类型,轻松满足数据可视化需求。”


1. 表格和样式

Pandas 的 DataFrame 提供了强大的样式功能,可以通过 Styler 对象实现类似 Excel 的条件着色效果。以下是关键方法和示例:

1.1. ​基础样式设置

通过 DataFrame.style 访问样式功能,支持链式调用:

1
df.style.set_caption("标题").set_properties(**{'background-color': 'lightgray'})

50%

1.2. ​条件着色

1.2.1. 单列条件着色


1
2
3
4
def color_negative_red(val):
    color = 'red' if val < 0.2 else 'black'
    return f'color: {color}'
df.style.applymap(color_negative_red)

50%

1.2.2. 多列条件着色

1
2
df.style.apply(lambda x: ['background: yellow' if v > 0.2 else '' for v in x], 
                        subset=['A', 'C'])

50%

1.2.3. ​极值高亮


1
df.style.highlight_max(color='lightgreen').highlight_min(color='pink')

50%

1.2.4. 渐变色背景

1
df.style.background_gradient(cmap='Blues', subset=['B'])

50%

1.2.5. 条形图样式

1
df.style.bar(subset=['C'], color='#5fba7d')

50%


1.2.6. 自定义表格样式

1
2
3
headers = {'selector': 'th',
    'props': 'background-color: #5e17eb; color: white;'}
df.style.set_table_styles([headers])

50%

1.2.7. 动态条件着色(复杂逻辑)​

1
2
3
4
5
def highlight_risk(row):
    # 当A列>90且B列<50时标黄
    return ['background: yellow' if (row['A']>0.3) & 
    (row['B']<0.5) else '' for _ in row]  # 返回与行等长的样式列表
df.style.apply(highlight_risk, axis=1)  # axis=1表示按行处理

50%

Notes

  • 样式仅在 Jupyter Notebook 或导出为 HTML 时生效,不支持直接修改原始数据。
  • 使用 subset 参数可限定着色范围。
  • 渐变色 (background_gradient) 支持调整色域范围 (low=0.2, high=0.8)。

2. Pandas 内置绘图功能

在 pandas 中,我们可能有多列数据,还有行标签和列标签。pandas 自身就有内置的方法,用于简化从 DataFrame 和 Series 绘制图形。

2.1. 线形图

Series 和 DataFrame 都有一个 plot 属性,用于绘制基本图表。默认情况下,plot() 生成的是线形图。

1
2
s = pd.Series(np.random.standard_normal(10).cumsum(), index=np.arange(0, 100, 10))
s.plot()

50%


该 Series 对象的索引会被传给matplotlib,并用于绘制 x 轴。可以通过 use_index=False 来禁用索引。x 轴的刻度和界限可以通过 xtick 和 xlim 选项进行调节,y 轴用 yticks 和 ylim 调节。plot 参数的部分列表参见下表:

参数 说明
alpha 图形填充透明度(0~1 之间)
ax matplotlib 的 Axes 对象,默认为当前 Axes (gca())
colormap 指定颜色映射(如 'viridis')
figsize 图像尺寸,格式为 (宽度, 高度)(单位:英寸)
fontsize 刻度标签字体大小
grid 是否显示网格线(默认为 None,遵循 matplotlib 默认样式)
kind 图形类型,可选:'line'(折线图,默认)、'bar'(柱状图)、'barh'(横向柱状图)、'hist'(直方图)、'box'(箱线图)、'kde'/'density'(核密度估计)、'area'(面积图)、'pie'(饼图)
label 图例标签名称
legend 是否显示图例(默认为 False)
logx/logy 是否对 x/y 轴使用对数刻度(默认为 False)
loglog 是否对 x/y 轴同时使用对数刻度
position 柱状图的柱子位置(需避免与 kind='bar' 冲突)
rot 刻度标签旋转角度(如 45 表示 45 度)
secondary_y 是否使用右侧的第二个 y 轴(默认为 False)
style 线条样式(如 'k--' 表示黑色虚线)
table 是否在图表下方显示数据表格(默认为 False)
title 图表标题(字符串)
use_index 是否使用 Series 的索引作为 x 轴刻度标签(默认为 True)
xerr/yerr 为柱状图添加误差线
xlim/ylim 设置 x/y 轴显示范围(格式:(min, max))
xticks/yticks 自定义 x/y 轴刻度值(列表)
**kwds 其他 matplotlib 绘图参数(如 color='red')

pandas 的大部分绘图方法都接收一个可选的 ax 参数,它可以是 matplotlib 的子图对象,这使你能够在网格布局中更为灵活地处理子图的位置。

DataFrame 的 plot 方法将各个列绘制成同一子图中的线,并自动创建图例。

1
2
3
4
5
df = pd.DataFrame(np.random.standard_normal((10, 4)).cumsum(0),
                  columns=['A', 'B', 'C', 'D'],
                  index=np.arange(0, 100, 10))
plt.style.use('grayscale')
df.plot()

50%

Notes

这里使用了 plt.style.use('grayscale') 将配色模式设置为灰度模式。

对于不同的绘图类型,plot 属性包含很多方法。例如,df.plot() 等价于 df.plot.line()。


Notes

plot 的额外关键字参数会传递给相应的 matplotlib 绘图函数,所以要更进一步自定义图表,就必须学习更多有关matplotlib API的知识。

DataFrame 还有一些用于对列进行灵活处理的选项。例如:要将所有列都绘制到同一个子图中还是分别创建各自的子图。下表展示了专属于DataFrame的plot参数:

参数 说明
subplots 是否为每一列数据创建子图,默认为 False
sharex 如果 subplots=True,是否共享 x 轴,默认为 True(当 ax=None 时)
sharey 如果 subplots=True,是否共享 y 轴,默认为 False
layout 子图的行列布局,格式为 (rows, columns)
legend 添加子图图例(默认为True)
sort_columns 是否按列名排序,默认为 False

2.2. 柱状图

plot.bar() 和 plot.barh() 分别用于绘制水平柱状图和垂直柱状图。对于柱状图,Series 或 DataFrame 的索引将被用作x轴(bar)或y轴(barh)的刻度。

1
2
3
4
fig, axes = plt.subplots(2,1)
data = pd.Series(np.random.uniform(size=16), index=list('abcdefghijklmnop'))
data.plot.bar(ax=axes[0], color='k', alpha=0.7)
data.plot.barh(ax=axes[1], color='k', alpha=0.7)

50%

对于 DataFrame,柱状图会将每一行的值分为一组,并排显示。

1
2
3
4
df = pd.DataFrame(np.random.uniform(size=(6, 4)),
        index=["one", "two", "three", "four", "five", "six"],
        columns=pd.Index(["A", "B", "C", "D"], name="Genus"))
df.plot.bar()

50%

注意,DataFrame 各列的名称“Genus”被用作图例标题。


传入stacked=True即可为DataFrame生成堆积柱状图,这样每行的值就会水平堆积在一起。

1
df.plot.bar(stacked=True,alpha=0.5)

50%

Pandas核心语法[6]

“Pandas 提供了强大的日期时间处理功能,从字符串到时间戳的转换、时区调整到格式化输出,都可以轻松实现。此外,字符串操作如替换、分割、过滤等,也能通过 str 访问器高效完成。”


1. 日期和时间

1.1. ​将字符串转换为日期时间格式

如果时间或日期数据是字符串格式,可以使用 pd.to_datetime() 函数将其转换为 Pandas 的 datetime 类型。

1
2
3
4
5
6
7
8
9
import pandas as pd

# 示例数据
data = {'date': ['2023-01-01', '2023-02-01', '2023-03-01']}
df = pd.DataFrame(data)

# 将 'date' 列转换为 datetime 类型
df['date'] = pd.to_datetime(df['date'])
print(df)

输出:

1
2
3
4
        date
0 2023-01-01
1 2023-02-01
2 2023-03-01

参数说明: - format:指定日期字符串的格式,例如 '%Y-%m-%d'。


  • errors:处理错误的方式,'raise'(报错)、'coerce'(将无效值转换为 NaT)、'ignore'(保留原值)。
  • unit:指定时间单位,如 's'(秒)、'ms'(毫秒)。

1.2. ​处理多种日期格式

如果日期字符串有多种格式,可以通过 errors='coerce' 参数忽略无法解析的日期,或者使用 format 参数指定格式。

1
2
3
4
5
6
data = {'date': ['2023-01-01', '01/02/2023', 'March 3, 2023']}
df = pd.DataFrame(data)

# 处理多种日期格式
df['date'] = pd.to_datetime(df['date'], errors='coerce')
print(df)

输出:

1
2
3
4
        date
0 2023-01-01
1 2023-01-02
2 2023-03-03

1.3. ​从时间戳转换

如果数据是时间戳(如 Unix 时间戳),可以使用 pd.to_datetime() 将其转换为 datetime 类型。


示例:

1
2
3
4
5
6
data = {'timestamp': [1672531199, 1672617599, 1672703999]}
df = pd.DataFrame(data)

# 将时间戳转换为 datetime
df['date'] = pd.to_datetime(df['timestamp'], unit='s')
print(df)

输出:

1
2
3
4
   timestamp                date
0  1672531199 2023-01-01 00:00:00
1  1672617599 2023-01-02 00:00:00
2  1672703999 2023-01-03 00:00:00

1.4. ​提取日期时间信息

转换后,可以使用 dt 访问器提取日期时间的各个部分,如年、月、日、小时等。

示例:

1
2
3
4
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
print(df)

输出:

1
2
3
4
                date  year  month  day
0 2023-01-01 00:00:00  2023      1    1
1 2023-01-02 00:00:00  2023      1    2
2 2023-01-03 00:00:00  2023      1    3

1.5. ​处理时区信息

如果数据包含时区信息,可以使用 tz_convert() 和 tz_localize() 进行时区转换。

示例:

1
2
3
4
5
# 添加时区信息
df['date'] = pd.to_datetime(df['date']).dt.tz_localize('UTC')
# 转换为本地时区
df['date'] = df['date'].dt.tz_convert('Asia/Shanghai')
print(df)

1.6. ​将日期时间转换为字符串

如果需要将 datetime 类型转换为特定格式的字符串,可以使用 dt.strftime()。

示例:


1
2
df['date_str'] = df['date'].dt.strftime('%Y-%m-%d %H:%M:%S')
print(df)

输出:

1
2
3
4
                date           date_str
0 2023-01-01 08:00:00  2023-01-01 08:00:00
1 2023-01-02 08:00:00  2023-01-02 08:00:00
2 2023-01-03 08:00:00  2023-01-03 08:00:00

2. 字符串操作

2.1. DataFrame 的字符串操作

在 Pandas 中,DataFrame 的字符串操作可以通过 str 访问器来实现。以下是一些常见的字符串操作方法:

2.1.1. 转换为大写或小写
1
2
df['column_name'] = df['column_name'].str.upper()  # 转换为大写
df['column_name'] = df['column_name'].str.lower()  # 转换为小写
2.1.2. 替换子字符串

1
df['column_name'] = df['column_name'].str.replace('old', 'new')  # 替换子字符串
2.1.3. 提取子字符串
1
df['new_column'] = df['column_name'].str[:3]  # 提取前 3 个字符
2.1.4. 分割字符串
1
df[['part1', 'part2']] = df['column_name'].str.split(' ', expand=True)  # 按空格分割
2.1.5. 检查是否包含子字符串
1
df['contains_substring'] = df['column_name'].str.contains('substring')  # 检查是否包含
2.1.6. 计算字符串长度
1
df['length'] = df['column_name'].str.len()  # 计算字符串长度
2.1.7. 去除空格
1
df['column_name'] = df['column_name'].str.strip()  # 去除两端空格

2.1.8. 正则表达式匹配
1
df['matches'] = df['column_name'].str.contains(r'\d')  # 检查是否包含数字

2.2. 排除科创板证券

科创板证券的代码通常以 688 开头。假设 DataFrame 中有一列 code 存放证券代码,可以通过以下方法排除科创板证券:

方法 1:使用 ~ 和 str.startswith()
1
df_filtered = df[~df['code'].str.startswith('688')]
方法 2:使用 str.contains() 和正则表达式
1
df_filtered = df[~df['code'].str.contains(r'^688')]
方法 3:使用 query() 方法
1
df_filtered = df.query("not code.str.startswith('688')", engine='python')

示例:

1
2
3
4
5
6
7
8
9
import pandas as pd

# 示例数据
data = {'code': ['600001', '688001', '000001', '688002'], 'name': ['A', 'B', 'C', 'D']}
df = pd.DataFrame(data)

# 排除科创板
df_filtered = df[~df['code'].str.startswith('688')]
print(df_filtered)

输出:

1
2
3
     code name
0  600001    A
2  000001    C

Notes

总结 - 字符串操作:通过 str 访问器可以实现大小写转换、替换、提取、分割、检查等操作。 - 排除科创板:使用 str.startswith() 或正则表达式过滤掉以 688 开头的证券代码。

Pandas核心语法[5]

“Pandas 提供了丰富的 IO 操作功能,支持从 CSV、SQL、Parquet 等多种文件格式中读取数据。通过优化参数如 chunksize、usecols 和 dtype,可以显著提升读取速度并减少内存占用。”


1. 数据预处理类

在 Pandas 中,DataFrame 的数据预处理是数据分析的关键步骤,包括数据清洗、缺失值处理、缩尾处理、去重等操作。fillnaclipwinsorizedropna 是数据预处理中常用的函数,用于处理缺失值、极端值以及修剪数据范围。以下是它们的详细介绍和用法:

1.1. fillna:填充缺失值

fillna 用于填充 DataFrame 或 Series 中的缺失值(NaN)。它支持多种填充方式,例如用固定值、前向填充、后向填充、均值填充等。

语法:

1
2
DataFrame.fillna(value=None, method=None, axis=None, inplace=False,
                 limit=None, downcast=None)

参数说明: - ​value:用于填充缺失值的值,可以是标量、字典、Series 或 DataFrame。 - ​method:填充方法,可选 'ffill'(前向填充)、'bfill'(后向填充)。 - ​axis:填充的轴,0 表示按行填充,1 表示按列填充。 - ​inplace:是否原地修改数据,默认为 False。 - ​limit:限制填充的最大连续缺失值数量。

示例:

1
2
3
4
5
6
import pandas as pd
import numpy as np

# 创建示例 DataFrame
data = {'A': [1, 2, np.nan], 'B': [np.nan, 5, 6]}
df = pd.DataFrame(data)


1
2
3
4
5
6
7
8
# 用 0 填充缺失值
df_filled = df.fillna(0)

# 前向填充
df_ffill = df.fillna(method='ffill')

# 用均值填充
df_mean_filled = df.fillna(df.mean())

1.2. ​clip:修剪数据范围

clip 用于将数据限制在指定的范围内,超出范围的值会被替换为边界值。

1
DataFrame.clip(lower=None, upper=None, axis=None, inplace=False)

参数说明: - lower:下限值,低于此值的会被替换为下限。 - ​upper:上限值,高于此值的会被替换为上限。 - axis:修剪的轴,0 表示按行修剪,1 表示按列修剪。 - ​inplace:是否原地修改数据。

示例:

1
2
# 将数据限制在 1 到 5 之间
df_clipped = df.clip(lower=1, upper=5)

1
2
3
4
# 对每列设置不同的上下限
lower = pd.Series([1, 2])
upper = pd.Series([4, 5])
df_clipped_custom = df.clip(lower=lower, upper=upper, axis=1)

1.3. ​winsorize:缩尾处理

winsorize 用于处理极端值,将超出指定分位数的值替换为分位数的值。它通常用于减少极端值对数据分析的影响。语法(通过 scipy.stats.mstats.winsorize):

1
2
from scipy.stats.mstats import winsorize
winsorize(data, limits=[lower_limit, upper_limit])

参数说明: - ​limits:指定上下分位数,例如 [0.05, 0.95] 表示将低于 5% 和高于 95% 的值替换为对应分位数的值。

示例:

1
2
3
4
from scipy.stats.mstats import winsorize

# 对数据进行上下 5% 的缩尾处理
df['A_winsorized'] = winsorize(df['A'], limits=[0.05, 0.95])

1.4. ​dropna:删除缺失值

dropna 用于删除包含缺失值的行或列。

1
DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)

参数说明: - axis:删除的轴,0 表示删除行,1 表示删除列。 - ​how:删除条件,'any'(默认)表示只要有一个缺失值就删除,'all' 表示只有全部为缺失值才删除。 - ​thresh:保留非缺失值的最小数量。 - ​subset:指定检查缺失值的列。 - ​inplace:是否原地修改数据。

示例:

1
2
3
4
5
6
7
8
# 删除包含缺失值的行
df_dropped = df.dropna()

# 删除包含缺失值的列
df_dropped_cols = df.dropna(axis=1)

# 只删除指定列中包含缺失值的行
df_dropped_subset = df.dropna(subset=['A'])

总结

  • fillna:用于填充缺失值,支持多种填充方式。
  • clip:用于将数据限制在指定范围内,处理极端值。
  • winsorize:用于缩尾处理,减少极端值的影响。
  • dropna:用于删除包含缺失值的行或列。

2. IO 操作

Pandas 中的 DataFrame 提供了丰富的 IO 操作功能,支持从多种文件格式中读取数据,并将数据写入到不同的文件格式中。

2.1. csv

2.1.1. 读取 csv

CSV 是最常用的文件格式之一,Pandas 提供了 read_csv 函数来读取 CSV 文件。

1
2
import pandas as pd
df = pd.read_csv('data.csv')

常用参数: - sep:指定分隔符,默认为逗号 ,。 - header:指定标题行,默认为 0(第一行)。


  • index_col:指定哪一列作为索引。
  • encoding:指定文件编码,如 utf-8 或 gbk。
  • na_values:指定哪些值应被视为缺失值。

示例:

1
df = pd.read_csv('data.csv', sep=';', header=0, index_col='ID', encoding='utf-8')
2.1.2. 读取 csv 时如何加速

在读取大型 CSV 文件时,除了基本的 pd.read_csv 操作外,可以通过以下方法显著提升读取速度:

[分块读取 (chunksize)]

对于非常大的文件,一次性加载到内存可能会导致内存不足。可以使用 chunksize 参数分块读取数据,逐块处理。

1
2
3
chunk_size = 10000  # 每次读取 10000 行
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    process(chunk)  # 自定义处理函数

这种方法可以有效减少内存占用,并允许边读边处理。

[指定列读取 (usecols)]

如果只需要部分列的数据,可以使用 usecols 参数指定要读取的列,避免加载不必要的数据。


1
df = pd.read_csv('large_file.csv', usecols=['column1', 'column2'])

这样可以减少内存使用并加快读取速度。

[优化数据类型 (dtype)]

Pandas 默认会推断每列的数据类型,但这可能会导致内存浪费。通过显式指定 dtype,可以减少内存占用并提升性能。

1
2
dtypes = {'column1': 'int32', 'column2': 'float32'}
df = pd.read_csv('large_file.csv', dtype=dtypes)

例如,将 int64 改为 int32 可以节省内存。

[使用更高效的解析器 (engine='pyarrow')]

Pandas 1.4 版本引入了 pyarrow 作为 CSV 解析器,相比默认的解析器,速度更快。

1
df = pd.read_csv('large_file.csv', engine='pyarrow')

pyarrow 支持并行解析,特别适合处理大文件

[​跳过无用数据 (skiprows, nrows)]

如果文件中有不需要的行或数据,可以使用 skiprows 跳过指定行,或使用 nrows 只读取前几行。


1
df = pd.read_csv('large_file.csv', skiprows=[1, 2], nrows=1000)

这样可以减少数据处理量。

[​并行处理 (Dask 或 Multiprocessing)]

对于非常大的数据集,可以使用并行处理工具如 Dask 来加速读取和处理。

1
2
3
import dask.dataframe as dd
df = dd.read_csv('large_file.csv')
result = df.groupby('column1').mean().compute()

Dask 会自动将文件分块并并行处理。

[​使用更高效的文件格式 (如 Parquet)]

如果可能,将 CSV 文件转换为 Parquet 格式,Parquet 是一种列式存储格式,读取速度更快。

1
df = pd.read_parquet('large_file.parquet', engine='fastparquet')

Parquet 文件不仅读取速度快,还能显著减少存储空间。

[​内存映射文件 (memory_map)]


对于特别大的文件,可以使用 memory_map 参数将文件映射到内存中,减少内存占用。

1
df = pd.read_csv('large_file.csv', memory_map=True)

这种方法适合处理超大型文件

2.1.3. 写入 csv

使用 to_csv 函数将数据写入 CSV 文件。

1
df.to_csv('output.csv', index=False)

常用参数: - index:是否写入索引,默认为 True。 - header:是否写入列名,默认为 True。 - encoding:指定文件编码。

2.2. pkl 和 hdf5

在 Pandas 中,DataFrame 可以方便地对 .pkl 和 .hdf5 文件进行读写操作。以下是详细的方法和示例: ​ .pkl 文件是 Python 的序列化文件格式,通常用于保存和加载 Python 对象,包括 DataFrame。使用 read_pickle 方法从 .pkl 文件中加载 DataFrame,使用 to_pickle 方法将 DataFrame 保存为 .pkl 文件。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd

# 创建示例 DataFrame
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})

# 保存为 .pkl 文件
df.to_pickle('data.pkl')

# 从 .pkl 文件加载 DataFrame
df = pd.read_pickle('data.pkl')
print(df)

.hdf5 是一种高效的存储格式,适合存储大规模数据。Pandas 提供了 HDFStore 和 to_hdf/read_hdf 方法来操作 .hdf5 文件。

1
2
3
4
5
6
# 保存为 .hdf5 文件
df.to_hdf('data.h5', key='df', mode='w')

# 从 .hdf5 文件加载 DataFrame
df = pd.read_hdf('data.h5', key='df')
print(df)

HDFStore 提供了更灵活的操作方式,支持多个数据集的存储和读取:

1
2
# 创建 HDFStore 对象
store = pd.HDFStore('data.h5')


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 存储多个 DataFrame
store.put('df1', df1)
store.put('df2', df2)

# 读取特定 DataFrame
df1 = store['df1']
df2 = store.get('df2')

# 关闭 HDFStore
store.close()

Notes

总结: - .pkl 文件:适合保存和加载单个 DataFrame,操作简单。 - .hdf5 文件:适合存储大规模数据,支持多个数据集和高效压缩。

2.3. parquet

Pandas 支持读取 Parquet 文件,使用 read_parquet 函数。

1
2
import pandas as pd
df = pd.read_parquet('data.parquet')

常用参数: - engine:指定引擎,如 pyarrow 或 fastparquet。 - columns:指定要读取的列。


示例:

1
df = pd.read_parquet('data.parquet', engine='pyarrow', columns=['col1', 'col2'])

2.4. html 和 md

Pandas 的 read_html 函数可以从网页中读取 HTML 表格数据。

1
2
3
url = 'http://example.com/table.html'
tables = pd.read_html(url)
df = tables[0]  # 获取第一个表格

如果需要处理复杂的网页数据,可以结合 requestsBeautifulSoup 库:

1
2
3
4
5
6
7
import requests
from bs4 import BeautifulSoup

response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
table = soup.find_all('table')[0]
df = pd.read_html(str(table))[0]

2.5. sql

Pandas 支持从 SQL 数据库中读取数据,使用 read_sql 函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import sqlite3

# 连接到数据库
conn = sqlite3.connect('database.db')

# 执行 SQL 查询并读取数据
df = pd.read_sql('SELECT * FROM table_name', conn)

# 关闭数据库连接
conn.close()

如果需要连接其他数据库(如 MySQL、PostgreSQL),可以使用相应的数据库驱动(如 pymysql、psycopg2)。

Pandas核心语法[4]

“在 Pandas 中,逻辑运算和比较运算是数据筛选的基础工具。通过与(&)、或(|)等操作符,可以轻松实现复杂条件筛选,比如选出市盈率最大且市净率最小的股票。”


1. 逻辑运算和比较

在 Pandas 中,DataFrame 的逻辑运算和比较是数据处理中常用的操作,主要用于筛选和过滤数据。以下是详细说明和实现方法:

1.1. DataFrame 的逻辑运算

逻辑运算包括与(&)、或(|)、非(~)和异或(^),通常与比较运算结合使用。示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd

# 创建示例 DataFrame
df = pd.DataFrame({
    'A': [True, False, True],
    'B': [False, True, False]
})

# 与运算
print(df['A'] & df['B'])

# 或运算
print(df['A'] | df['B'])

# 非运算
print(~df['A'])

# 异或运算
print(df['A'] ^ df['B'])

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
0    False
1    False
2    False
dtype: bool
0     True
1     True
2     True
dtype: bool
0    False
1     True
2    False
dtype: bool
0     True
1     True
2     True
dtype: bool

1.2. DataFrame 的比较运算

比较运算包括 >、<、==、!=、>=、<=,返回布尔值组成的 DataFrame 或 Series。

示例代码:

1
2
3
4
5
# 创建示例 DataFrame
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

1
2
3
# 比较运算
print(df['A'] > 1)  # 返回布尔 Series
print(df > 2)       # 返回布尔 DataFrame

输出结果:

1
2
3
4
5
6
7
8
0    False
1     True
2     True
Name: A, dtype: bool
       A      B
0  False   True
1  False   True
2   True   True

Dataframe中包含了我们提取的特征。要选取PE最大同时是PB最小的前30列,怎么做?

假设 DataFrame 中包含以下特征: - PE:市盈率 - PB:市净率

1
2
3
4
5
6
7
8
import pandas as pd

# 创建示例 DataFrame
data = {
    'PE': [10, 20, 30, 40, 50],
    'PB': [1.5, 1.2, 1.0, 0.8, 0.5]
}
df = pd.DataFrame(data)

选取 PE 最大且 PB 最小的前 30 列, 要实现这一需求,可以按照以下步骤操作: 1. 计算 PE 的最大值和 PB 的最小值。 2. 根据条件筛选数据。 3. 选取前 30 列。

1
2
3
4
5
6
# 筛选 PE 最大且 PB 最小的行
filtered_df = df[(df['PE'] == df['PE'].max()) & (df['PB'] == df['PB'].min())]

# 选取前 30 列(假设列数足够)
result = filtered_df.iloc[:, :30]
print(result)

输出结果:

1
2
   PE   PB
4  50  0.5

2. 分组运算(groupby)

在 Pandas 中,groupby 是用于对 DataFrame 进行分组运算的核心方法。它遵循“拆分-应用-合并”的逻辑,即先将数据按指定条件分组,然后对每个分组执行操作,最后将结果合并。以下是详细说明和具体实现方法:

2.1. groupby 的基本语法


groupby 的基本语法为:

1
df.groupby(by=分组键)[选择列].聚合函数
  • by:指定分组的列名或列名列表。
  • ​选择列:可选,指定需要操作的列。
  • ​聚合函数:如 sum()、mean()、max() 等。

示例:

1
2
3
4
5
6
7
8
9
import pandas as pd
# 创建示例 DataFrame
data = {'行业': ['科技', '科技', '金融', '金融', '科技'],
        '公司': ['A', 'B', 'C', 'D', 'E'],
        'PE': [30, 25, 15, 20, 35]}
df = pd.DataFrame(data)
# 按行业分组并计算平均 PE
result = df.groupby('行业')['PE'].mean()
print(result)

输出结果:

1
2
3
4
行业
科技    30.0
金融    17.5
Name: PE, dtype: float64

2.2. groupby 的应用

假设你的因子分析数据表包含以下列: - ​行业标签:表示公司所属的行业。 - ​PE值:表示公司的市盈率。

示例数据:

1
2
3
4
data = {'行业': ['科技', '科技', '金融', '金融', '科技', '金融'],
        '公司': ['A', 'B', 'C', 'D', 'E', 'F'],
        'PE': [30, 25, 15, 20, 35, 10]}
df = pd.DataFrame(data)

选出每个行业 PE 最强的 5 支, “PE 最强”可以理解为 PE 值最高的公司。以下是实现步骤: 1. 按行业分组。 2. 对每个分组按 PE 值降序排序。 3. 选取每个分组的前 5 行。

1
2
3
4
5
6
# 按行业分组,并对每个分组按 PE 值降序排序
grouped = df.groupby('行业', group_keys=False)

# 选取每个行业 PE 值最高的 5 家公司
result = grouped.apply(lambda x: x.nlargest(5, 'PE'))
print(result)

输出结果:


1
2
3
4
5
6
7
   行业 公司  PE
0  科技  A  30
4  科技  E  35
1  科技  B  25
2  金融  C  15
3  金融  D  20
5  金融  F  10

3. 多重索引和高级索引

在 Pandas 中,DataFrame 的多重索引(MultiIndex)和高级索引是处理复杂数据结构的重要工具。它们允许你在一个轴上创建多个层级的索引,从而更灵活地组织和访问数据。以下是详细说明:

3.1. ​多重索引(MultiIndex)​

多重索引是指在一个轴上(行或列)拥有多个层级的索引。它适用于处理具有层次化结构的数据,例如按地区和时间分类的数据。

3.1.1. 创建多重索引

Pandas 提供了多种方法创建多重索引,以下是常见的方式:

[​从数组创建]

1
2
3
4
import pandas as pd
arrays = [['A', 'A', 'B', 'B'], [1, 2, 1, 2]]
multi_index = pd.MultiIndex.from_arrays(arrays, 
                names=('Letter', 'Number'))


1
2
df = pd.DataFrame({'Value': [10, 20, 30, 40]}, index=multi_index)
print(df)

[从元组创建]

1
2
3
4
tuples = [('A', 1), ('A', 2), ('B', 1), ('B', 2)]
multi_index = pd.MultiIndex.from_tuples(tuples, names=('Letter', 'Number'))
df = pd.DataFrame({'Value': [10, 20, 30, 40]}, index=multi_index)
print(df)

[从笛卡尔积创建]

1
2
3
4
5
letters = ['A', 'B']
numbers = [1, 2]
multi_index = pd.MultiIndex.from_product([letters, numbers], names=('Letter', 'Number'))
df = pd.DataFrame({'Value': [10, 20, 30, 40]}, index=multi_index)
print(df)

3.1.2. ​访问多重索引数据

使用 loc 访问:

1
print(df.loc[('A', 1)])  # 访问特定行

​使用 xs 交叉选择:


1
print(df.xs(1, level='Number'))  # 获取第二层级索引为 1 的所有行

​使用切片器:

1
print(df.loc[pd.IndexSlice[:, 2], :])  # 获取第二层级索引为 2 的所有行

3.1.3 ​操作多重索引

​交换层级:

1
2
df_swapped = df.swaplevel(0, 1)
print(df_swapped)

​重排序层级:

1
2
df_sorted = df.sort_index(level='Number')
print(df_sorted)

​重置索引:

1
2
df_reset = df.reset_index()
print(df_reset)

3.2. ​高级索引


高级索引是指在多重索引的基础上,使用更灵活的方法选择和操作数据。

3.2.1. ​使用 reindex 重新索引

reindex 可以根据指定的索引重新排列数据,并填充缺失值:

1
2
3
new_index = [('B', 2), ('A', 1), ('C', 3)]
df_reindexed = df.reindex(new_index)
print(df_reindexed)
3.2.2. ​使用 align 对齐索引

align 可以将两个具有不同索引的 DataFrame 对齐:

1
2
3
4
5
df1 = pd.DataFrame({'Value': [10, 20]}, index=[('A', 1), ('B', 2)])
df2 = pd.DataFrame({'Value': [30, 40]}, index=[('B', 2), ('C', 3)])
aligned_df1, aligned_df2 = df1.align(df2)
print(aligned_df1)
print(aligned_df2)

Notes

  • 多重索引:通过 MultiIndex 创建多层级索引,支持灵活的数据组织和访问。
  • ​高级索引:通过 reindex、align、groupby 等方法实现复杂的数据操作。

通过熟练掌握多重索引和高级索引,可以更高效地处理和分析复杂数据。


4. 窗口函数

Pandas 中的窗口函数(Window Functions)是一种强大的工具,用于对数据进行滑动窗口计算。它们通常用于时间序列数据或有序数据,支持滚动计算、扩展计算和指数加权移动等操作。以下是 Pandas 中窗口函数的详细说明。

4.1. ​窗口函数的基本概念

窗口函数是一种特殊的函数,它在一个固定大小的窗口内对数据进行计算,并返回与原始数据相同数量的结果。常见的窗口函数包括:

  • ​滚动窗口(Rolling Window)​:在一个固定大小的窗口内对数据进行计算。
  • ​扩展窗口(Expanding Window)​:从第一个数据点开始,逐步增加窗口大小,直到包含所有数据点。
  • ​指数加权移动窗口(Exponentially Weighted Moving Window)​:对较近的数据赋予更高的权重,较远的数据赋予较低的权重。

4.2. ​滚动窗口(Rolling Window)​

滚动窗口用于在固定大小的窗口内对数据进行计算。例如,计算过去 5 天的平均值或最大值。

1
2
3
4
5
import pandas as pd

# 创建示例 DataFrame
data = {'value': [1, 2, 3, 4, 5, 6, 7, 8, 9]}
df = pd.DataFrame(data)

1
2
3
# 计算滚动平均值,窗口大小为 3
df['rolling_mean'] = df['value'].rolling(window=3).mean()
print(df)

参数说明: - ​window:窗口大小。 - ​min_periods:窗口中需要的最小数据点数量,否则结果为 NaN。 - ​center:是否以当前行为中心划分窗口。

4.3. 扩展窗口(Expanding Window)​

扩展窗口从第一个数据点开始,逐步增加窗口大小,直到包含所有数据点。它通常用于计算累计和、累计平均等。

1
2
3
# 计算累计和
df['expanding_sum'] = df['value'].expanding().sum()
print(df)

4.4. 指数加权移动窗口(Exponentially Weighted Moving Window)​

指数加权移动窗口对较近的数据赋予更高的权重,较远的数据赋予较低的权重。它在金融数据分析中非常有用。


1
2
3
# 计算指数加权移动平均
df['ewm_mean'] = df['value'].ewm(span=3).mean()
print(df)

参数说明: - ​span:指定衰减系数。 - ​alpha:直接指定衰减因子。

Notes

  • 窗口大小的选择:根据具体应用场景和数据特点选择窗口大小,过小可能导致结果波动较大,过大可能掩盖重要细节。
  • ​边界值处理:使用 min_periods 参数控制最小窗口大小,避免 NaN 值。
  • 数据缺失处理:使用 fillna() 填充缺失值或 dropna() 删除缺失值。

5. 数学运算和统计

在 Pandas 中,DataFrame 提供了丰富的数学运算和统计功能,能够方便地对数据进行计算和分析。

5.1. ​数学运算

Pandas 支持对 DataFrame 进行基本的数学运算,包括加法、减法、乘法、除法等。这些运算可以逐元素进行,也可以对整列或整行进行操作。 示例代码:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd

# 创建示例 DataFrame
data = {'A': [1, 2, 3], 'B': [4, 5, 6]}
df = pd.DataFrame(data)

# 加法
df['C'] = df['A'] + df['B']

# 减法
df['D'] = df['A'] - df['B']

# 乘法
df['E'] = df['A'] * df['B']

# 除法
df['F'] = df['A'] / df['B']

print(df)

输出结果:

1
2
3
4
   A  B  C  D   E    F
0  1  4  5 -3   4  0.25
1  2  5  7 -3  10  0.40
2  3  6  9 -3  18  0.50

其他数学运算: - ​幂运算:df['A'] ​** 2 - ​平方根:df['A'].pow(0.5) - ​对数运算:df['A'].apply(np.log)(需导入 numpy 库)


5.2. ​统计计算

Pandas 提供了多种统计方法,用于对 DataFrame 中的数据进行分析。

常用统计方法: - ​求和:df.sum() - ​平均值:df.mean() - ​最大值:df.max() - ​最小值:df.min() - ​标准差:df.std() - ​方差:df.var() - ​中位数:df.median() - ​众数:df.mode() - ​分位数:df.quantile(q=0.25)(计算 25% 分位数)

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 计算各列的和
sum_result = df.sum()

# 计算各列的平均值
mean_result = df.mean()

# 计算各列的最大值
max_result = df.max()

# 计算各列的最小值
min_result = df.min()

1
2
3
4
5
6
7
# 计算各列的标准差
std_result = df.std()

# 计算描述性统计信息
desc_stats = df.describe()

print(desc_stats)

输出结果:

1
2
3
4
5
6
7
8
9
              A         B         C         D         E         F
count  3.000000  3.000000  3.000000  3.000000  3.000000  3.000000
mean   2.000000  5.000000  7.000000 -3.000000 10.666667  0.383333
std    1.000000  1.000000  2.000000  0.000000  7.023796  0.125833
min    1.000000  4.000000  5.000000 -3.000000  4.000000  0.250000
25%    1.500000  4.500000  6.000000 -3.000000  7.000000  0.325000
50%    2.000000  5.000000  7.000000 -3.000000 10.000000  0.400000
75%    2.500000  5.500000  8.000000 -3.000000 14.000000  0.450000
max    3.000000  6.000000  9.000000 -3.000000 18.000000  0.500000

5.3. ​高级统计功能

Pandas 还支持更复杂的统计操作,例如: - ​累计统计:df.cumsum()(累计和)、df.cummax()(累计最大值) - ​相关性分析:df.corr()(计算相关系数矩阵) - ​协方差分析:df.cov()(计算协方差矩阵) - ​偏度和峰度:df.skew()(偏度)、df.kurtosis()(峰度)

示例代码:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 计算累计和
cumsum_result = df.cumsum()

# 计算相关系数矩阵
corr_matrix = df.corr()

# 计算协方差矩阵
cov_matrix = df.cov()

print(corr_matrix)

5.4. ​分组统计

Pandas 的 groupby 方法可以对数据进行分组,然后对每个分组进行统计计算。示例代码:

1
2
3
4
5
6
7
# 创建示例 DataFrame
data = {'Category': ['A', 'B', 'A', 'B', 'A'], 'Value': [10, 20, 30, 40, 50]}
df = pd.DataFrame(data)

# 按 Category 分组并计算每组的平均值
grouped_stats = df.groupby('Category').mean()
print(grouped_stats)
1
2
3
4
          Value
Category       
A           30.0
B           30.0

Pandas核心语法[3]

“DataFrame 是 Pandas 的核心数据结构,支持多种数据类型和灵活的操作方式。无论是嵌套字典、NumPy 数组还是 CSV 文件,都可以轻松转换为 DataFrame,助你快速完成数据分析任务。”


1. 快速探索 DataFrame

1.1. 创建 DataFrame

DataFrame 含有一组有序且有命名的列,每一列可以是不同的数据类型(数值、字符串、布尔值等)。DataFrame 既有行索引也有列索引,可以看作由共用同一个索引的Series组成的字典。虽然DataFrame是二维的,但利用层次化索引,仍然可以用其表示更高维度的表格型数据。如果你使用的是Jupyter notebook,pandas的DataFrame 对象将会展示为对浏览器更为友好的HTML表格。

1.1.1. 传入一个由等长列表或Numpy数组构成的字典
1
2
3
4
5
6
import pandas as pd
data = {"state":["Ohio","Ohio","Ohio","Nevada","Nevada","Nevada"],
       "year":[2000,2001,2002,2003,2004,2005],
       "pop":[1.5,1.7,3.6,2.4,2.9,3.2]}
frame = pd.DataFrame(data)
frame

50%

生成的DataFrame会自动加上索引(和Series一样),且全部列会按照data键(键的顺序取决于在字典中插入的顺序)的顺序有序排列。 (如果你使用的是Jupyter notebook,pandas的DataFrame对象会展示为对浏览器更为友好的HTML表格)


对于特别大的DataFrame,可以使用head方法,只展示前5行。相似地,tail方法会返回最后5行。

1
2
print(frame.head())
print(frame.tail())

50%

下面还有几种创建DataFrame的情况:

1
2
3
4
5
# 如果指定了列的顺序,则DataFrame的列就会按照指定顺序进行排列
frame1 = pd.DataFrame(data=data,columns=["year","state","pop"])

# 如果字典中不包括传入的列,就会在结果中产生缺失值
frame2 = pd.DataFrame(data=data,columns=["year","state","pop","debt"])

1.1.2. 传入嵌套字典

如果将嵌套字典传给DataFrame,pandas就会将外层字典的键解释为列,将内层字典的键解释为行索引。

1
2
3
populations = {"Ohio":{2000:1.5,2001:1.7,2002:3.6},"Nevada":{2001:2.4,2002:2.9}}
frame3 = pd.DataFrame(populations)
frame3

50%

使用类似于Numpy数组的方法,可以对 DataFrame 进行转置(交换行和列):

1
frame3.T

50%

内层字典的键被合并后形成了结果的索引。如果明确指定了索引,则不会这样:


1
pd.DataFrame(populations,index=["2001","2002","2003"])

50%

由Series组成的字典差不多也是一样的用法:

1
2
pdata = {"Ohio":frame3["Ohio"][:-1],"Nevada":frame3["Nevada"][:2]}
pd.DataFrame(pdata)

50%

可以向DataFrame构造器输入的数据:


类型 说明
字典 (Dict) 键为列名,值为列表、NumPy 数组或 Series。每列的长度必须一致。
列表 (List) 列表中的每个元素是一个字典,字典的键为列名,值为对应列的数据。
NumPy 数组 二维数组,每行对应 DataFrame 的一行,每列对应 DataFrame 的一列。
Series 单个 Series 可以构造单列的 DataFrame,多个 Series 可以构造多列。
结构化数组 NumPy 的结构化数组,字段名对应 DataFrame 的列名。
其他 DataFrame 可以通过复制另一个 DataFrame 来创建新的 DataFrame。
CSV 文件 通过 pd.read_csv() 读取 CSV 文件并转换为 DataFrame。
Excel 文件 通过 pd.read_excel() 读取 Excel 文件并转换为 DataFrame。
JSON 数据 通过 pd.read_json() 读取 JSON 数据并转换为 DataFrame。
SQL 查询结果 通过 pd.read_sql() 读取 SQL 查询结果并转换为 DataFrame。
HTML 表格 通过 pd.read_html() 从 HTML 页面中提取表格并转换为 DataFrame。
剪贴板数据 通过 pd.read_clipboard() 从剪贴板中读取数据并转换为 DataFrame。

如果设置了DataFrame的index和columns的name属性,则这些信息也会显示出来:

1
2
3
frame3.index.name = "year"
frame3.columns.name = "state"
frame3

50%

1
2
3
4
5
# 二维的ndarray的DataFrame形式返回
frame3.to_numpy()

# 如果DataFrame各列的数据类型不同,则返回数组会选用能兼容所有列的数据类型:
frame2.to_numpy()

50%

1.2. DataFrame 的合并和连接

在 Pandas 中,concat、join 和 merge 是用于合并和连接 DataFrame 的三种主要方法。它们各有不同的用途和适用场景,以下是详细说明。


1.2.1. ​concat:基于轴的数据拼接

concat 主要用于沿指定轴(行或列)将多个 DataFrame 或 Series 拼接在一起。

参数说明: - ​objs:需要拼接的 DataFrame 或 Series 列表。 - ​axis:拼接方向,0 表示按行拼接(默认),1 表示按列拼接。 - join:拼接方式,'outer'(默认,保留所有索引)或 'inner'(仅保留共同索引)。 - ignore_index:是否忽略原索引并生成新索引,默认为 False。 - keys:为拼接后的数据添加层次化索引。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})

# 按行拼接
result = pd.concat([df1, df2], axis=0)
print(result)

# 按列拼接
result = pd.concat([df1, df2], axis=1)
print(result)

适用场景: - 将多个结构相似的数据集简单堆叠在一起。 - 不需要基于键值匹配,只需按行或列拼接。


1.2.2. merge:基于键值的合并

merge 用于根据一个或多个键将两个 DataFrame 合并在一起,类似于 SQL 中的 JOIN 操作。

参数说明: - ​left:左侧的 DataFrame。 - right:右侧的 DataFrame。 - ​how:合并方式,可选 'inner'(默认,内连接)、'left'(左连接)、'right'(右连接)、'outer'(外连接)。 - ​on:用于合并的列名(键),必须在两个 DataFrame 中都存在。 - left_on/right_on:当两个 DataFrame 的键列名不同时,分别指定左侧和右侧的键列。 - suffixes:当两个 DataFrame 中存在重复列名时,用于区分的后缀。

示例:

1
2
3
4
5
6
7
8
9
df1 = pd.DataFrame({'key': ['A', 'B', 'C'], 'value1': [1, 2, 3]})
df2 = pd.DataFrame({'key': ['B', 'C', 'D'], 'value2': [4, 5, 6]})

# 内连接
result = pd.merge(df1, df2, on='key', how='inner')
print(result)
# 外连接
result = pd.merge(df1, df2, on='key', how='outer')
print(result)

适用场景: - 需要根据某些列(键)将两个表关联。 - 类似于 SQL 中的 JOIN,适合处理结构化数据。


1.2.3. join:基于索引的合并

join 是基于索引将两个 DataFrame 合并在一起,是 merge 的简化版。 参数说明: - other:要连接的另一个 DataFrame。 - ​on:用于连接的列名或索引。 - ​how:连接方式,可选 'left'(默认,左连接)、'right'(右连接)、'outer'(外连接)、'inner'(内连接)。 - ​lsuffix/rsuffix:当两个 DataFrame 中存在重复列名时,分别指定左侧和右侧的后缀。

示例:

1
2
3
4
5
6
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}, index=['x', 'y'])
df2 = pd.DataFrame({'C': [5, 6], 'D': [7, 8]}, index=['x', 'y'])

# 基于索引的左连接
result = df1.join(df2, how='left')
print(result)

适用场景: - 基于索引进行简单的数据合并。 - 适合处理索引对齐的数据。

方法 主要用途 适用场景 灵活性 性能
concat 基于轴的数据拼接 简单堆叠数据,结构相似的数据集 按行或列拼接 适合大规模数据
merge 基于键值的合并,类似于 SQL 的 JOIN 结构化数据,需要关联表 支持多种连接方式 适合小规模数据
join 基于索引的合并 索引对齐的数据 简化版 merge 适合简单操作

1.3. 删除行和列

关键字del可以像在字典中那样删除列。

1
2
3
4
5
frame2["eastern"] = frame2["state"] = "ohio"
print(frame2)

del frame2["eastern"]
print(frame2.columns)

50%

1.1.4. 定位、读取和修改

1.1.4.1. 获取列

通过类似于字典标记或点属性的方式,可以将DataFrame的列获取为一个Series。

1
2
print(frame2["state"])
print(frame2.year)

50%

如果列名包含空格或下划线以外的符号,是不能用点属性的方式访问的。

1.1.4.2. 修改列
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np
# 通过赋值的方法修改列
frame2["debt"] = 16.5
print(frame2)
frame2["debt"] = np.arange(6.)
print(frame2)

# 将列表和数组赋值给某个列
val = pd.Series([1.2,-1.5,-1.7],index=["two","four","five"])  # 长度必须与DataFrame保持一致
frame2["debt"] = val
print(frame2)

50%


1.1.4.2. 获取行(iloc和loc)

通过ilocloc属性,也可以通过位置或名称的方式进行获取行。

1
2
print(frame2.loc[1])
print(frame2.iloc[2])

50%

1.1.5. 转置

转置操作是将 DataFrame 的行和列进行互换,即将行变为列,列变为行。Pandas 提供了两种方法来实现转置: - T 属性:直接调用 DataFrame.T 进行转置。 - ​transpose() 方法:通过 DataFrame.transpose() 实现转置。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd

# 创建一个示例 DataFrame
data = {'Name': ['Tom', 'Jack', 'Steve'], 'Age': [28, 34, 29], 
        'City': ['London', 'New York', 'Sydney']}
df = pd.DataFrame(data)

# 使用 T 属性转置
transposed_df = df.T
print(transposed_df)

# 使用 transpose() 方法转置
transposed_df = df.transpose()
print(transposed_df)

注意事项: - 转置操作会改变 DataFrame 的结构,但不会修改原始数据。 - 如果 DataFrame 包含混合数据类型,转置后可能需要重新调整数据类型。

1.1.6. 重采样(resample)

重采样是将时间序列数据从一个频率转换为另一个频率的过程。Pandas 提供了 resample() 方法来实现重采样,支持以下两种类型: - ​上采样(Upsampling)​:将低频数据转换为高频数据(如将日数据转换为小时数据)。 - ​下采样(Downsampling)​:将高频数据转换为低频数据(如将分钟数据转换为小时数据)。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd

# 创建一个示例时间序列 DataFrame
data = {'date': pd.date_range(start='1/1/2020', periods=100, freq='D'),
        'price': range(100)}
df = pd.DataFrame(data)
df.set_index('date', inplace=True)

# 下采样:将日数据转换为月数据,并计算每月的平均价格
monthly_avg_price = df['price'].resample('M').mean()
print(monthly_avg_price)

# 上采样:将日数据转换为小时数据,并使用前向填充
hourly_price = df['price'].resample('H').ffill()
print(hourly_price)

注意事项: - 重采样需要 DataFrame 的索引为时间类型。 - 可以使用 fillna()interpolate() 方法处理缺失值。


Pandas核心语法[2]

1. Series 的基本功能

本节,我们将介绍Series的一些数据的基本操作方法。后续将会深入地挖掘pandas在数据分析和处理方面的功能。


1.1. 重建索引

重建索引是通过 reindex() 方法实现的,它允许用户根据新的索引标签对 Series 进行重排或填充。如果新索引标签在原 Series 中不存在,默认会用 NaN 填充。

参数 描述 默认值
index 新的索引标签列表,可以是 Index 实例或其他序列型数据结构。 None
method 填充缺失值的方法,可选值包括:'backfill'/'bfill'(后向填充)、'pad'/'ffill'(前向填充)、'nearest'(最近填充)。 None
fill_value 用于填充缺失值的默认值。 NaN
limit 使用填充方法时的最大填充距离。 None
tolerance 最大容差,超出此范围则不填充。 None
level 如果索引是 MultiIndex,则指定使用哪一级别进行重新索引。 None
copy 如果为 True,即使新旧索引相同也会返回一个新的副本。 True

示例 1:基本重建索引

1
2
3
4
5
6
7
8
s = Series([1, 2, 3], index=['a', 'b', 'c'])

# 新的索引
new_index = ['a', 'b', 'c', 'd']

# 重建索引
s_reindexed = s.reindex(new_index, fill_value=0)
print(s_reindexed)


50%

示例 2:使用填充方法 对于时间序列这样的有序数据,重建索引时可能需要做一些插值或填值处理。method选项可以达到此目的,例如使用ffill可以实现前向填充。

1
2
3
# 使用前向填充
s_reindexed = s.reindex(new_index, method='ffill')
print(s_reindexed)

50%


示例 3:指定填充值

1
2
3
# 指定填充值为 -1
s_reindexed = s.reindex(new_index, fill_value=-1)
print(s_reindexed)

50%

1.2. 删除指定轴上的项

在 Pandas 中,Series 删除指定轴上的项可以通过 drop() 方法实现。Series 是一维数据结构,因此删除操作通常是针对索引(行)进行的。

drop() 方法用于删除指定的索引标签,并返回一个新的 Series,不会修改原对象。其语法如下:

1
Series.drop(labels, axis=0, inplace=False, errors='raise')
  • labels: 要删除的索引标签,可以是单个标签或标签列表。
  • axis: 指定操作的轴,对于 Series 只能是 0(默认值),表示删除行。
  • inplace: 如果为 True,则直接在原对象上修改,不返回新对象。
  • errors: 如果指定标签不存在,raise 会抛出错误,ignore 会忽略错误。

1
2
3
4
5
6
7
obj = Series(np.arange(5.),index=["a","b","c","d","e"])
print(obj)

new_obj = obj.drop("c")
print(new_obj)

print(obj.drop(["d","c"]))

1.3. 索引、选取和过滤

[索引选取] Series索引的工作方式类似于Numpy数组的索引,只不过Series的索引值可以不仅仅是整数:

1
2
3
s = Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(s['b'])
print(s[['a', 'c']])  # 输出:a 10, c 30

Series 的选取可以通过以下方式实现: - 基本选取:使用 [] 运算符。 - 属性选取:使用 . 运算符(仅适用于标签为合法变量名的情况)。 - iloc 和 loc:分别用于位置索引和标签索引。

1
2
3
4
5
# iloc 选取
print(s.iloc[1])  # 输出:20

# loc 选取
print(s.loc['b'])  # 输出:20

[索引过滤] Series 的过滤可以通过以下方式实现:

  • 布尔索引:使用布尔条件筛选数据。
  • 条件表达式:结合条件表达式进行过滤。
  • isin() 方法:筛选值是否在指定列表中。
  • where() 和 mask() 方法:根据条件替换或保留数据。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 布尔索引
print(s[s > 20])  # 输出:c 30, d 40

# 条件表达式
print(s[s % 20 == 0])  # 输出:b 20, d 40

# isin() 方法
print(s[s.isin([10, 30])])  # 输出:a 10, c 30

# where() 方法
print(s.where(s > 20, -1))  # 输出:a -1, b -1, c 30, d 40

# mask() 方法
print(s.mask(s > 20, -1))  # 输出:a 10, b 20, c -1, d -1

1.4. 算数运算和数据对齐

1
2
3
4
5
6
7
# 创建两个具有不同索引的 Series
s1 = Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = Series([4, 5, 6], index=['b', 'c', 'd'])

# 自动对齐索引并相加
result = s1 + s2
print(result)

50%

如果你使用过数据库,可以认为这类似于join操作。

如果不想得到 NaN,可以使用 fill_value 参数指定一个默认值来填充缺失的索引:

1
2
result = s1.add(s2, fill_value=0)
print(result)

Notes

pandas 的isnullnotnull函数可以用于检测缺失数据,不妨来试一试!

1.5. 排序和排名

Series 的排序可以通过以下两种方式实现:(1) 按索引排序:使用 sort_index() 方法。(2) 按值排序:使用 sort_values() 方法。


[按索引排序] sort_index() 方法用于对 Series 的索引进行排序。默认情况下,索引按升序排列。

1
2
3
4
5
6
7
s = Series([4, 1, 2, 3], index=['d', 'a', 'c', 'b'])

# 按索引升序排序
print(s.sort_index())  # 输出:a 1, b 3, c 2, d 4

# 按索引降序排序
print(s.sort_index(ascending=False))  # 输出:d 4, c 2, b 3, a 1

参数: - ascending:是否升序排序,默认为 True。 - inplace:是否在原对象上修改,默认为 False。

[按值排序] sort_values() 方法用于对 Series 的值进行排序。默认情况下,值按升序排列。

1
2
3
4
# 按值升序排序
print(s.sort_values())  # 输出:a 1, c 2, b 3, d 4
# 按值降序排序
print(s.sort_values(ascending=False))  # 输出:d 4, b 3, c 2, a 1

参数: - ascending:是否升序排序,默认为 True。 - inplace:是否在原对象上修改,默认为 False。 - na_position:缺失值的位置,默认为 last(排在最后)。


[排名] Series 的排名通过 rank() 方法实现,它为每个值分配一个排名,支持多种排名方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
s = Series([7, -5, 7, 4, 2, 0, 4])

# 默认排名(平均排名)
print(s.rank())  # 输出:0 6.5, 1 1.0, 2 6.5, 3 4.5, 4 3.0, 5 2.0, 6 4.5

# 最小排名
print(s.rank(method='min'))  # 输出:0 6.0, 1 1.0, 2 6.0, 3 4.0, 4 3.0, 5 2.0, 6 4.0

# 最大排名
print(s.rank(method='max'))  # 输出:0 7.0, 1 1.0, 2 7.0, 3 5.0, 4 3.0, 5 2.0, 6 5.0

# 按出现顺序排名
print(s.rank(method='first'))  # 输出:0 6.0, 1 1.0, 2 7.0, 3 4.0, 4 3.0, 5 2.0, 6 5.0

参数: - method:排名方法,可选值包括: - 'average'(默认):相同值分配平均排名。 - 'min':相同值分配最小排名。 - 'max':相同值分配最大排名。 - 'first':相同值按出现顺序分配排名。 - 'dense':相同值分配相同排名,且排名不跳跃。 - ascending:是否升序排名,默认为 True。 - na_option:缺失值的处理方式,可选 'keep'(保留)、'top'(排在最前)、'bottom'(排在最后)。

1.6. 带有重复标签的轴索引


直到目前为止,几乎所有示例的轴标签(索引值)都是唯一的。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。观察下面这个带有重复索引值的Series:

1
2
obj = Series(np.arange(5),index=["a","a","b","b","c"])
obj

50%

索引的is_unique属性可以告诉我们索引值是否唯一:

1
obj.index.is_unique  # False

对于带有重复值的索引,数据选取操作将会有些不同。如果某个标签对应多个项,则返回Series;如果对应于单个项,则返回标量值:


1
2
print(obj["a"])
print(obj["c"])

50%

Pandas核心语法[1]

Pandas在量化交易中,处于核心地位。许多基于Python SDK的数据源返回的数据格式一般是pandas.DataFrame。因子分析库Alphalens、性能评估库empyrical等都依赖于Pandas。


这并不奇怪。因为Pandas的开发者Wes McKinney原本是资管公司AQR Capital Management的研究员。他在处理大量金融分析任务时,发现Python的现有工具(如NumPy)无法高效处理结构化数据分析,于是在2008年开始开发Pandas,并于2009年将其开源。

1.1. 基本数据结构

Pandas 的核心数据结构是 Series(类似于一维数组)和 DataFrame(矩形的数据表)数据结构。

1.2. Series

Series 由一组数据以及一组与之相关的数据标签(即索引)组成。仅由一个数组即可创建最简单的Series。Series以交互式的方法呈现,索引位于左边,值位于右边(一般会自动创建一个从 0 到 N-1 的索引,这里的 N 为数据长度)。与Numpy数组相比,我们可以通过索引的标签选取Series中的单个或一组值。还可以将其看作长度固定的有序字典,在可能使用字典的场景中,也可以使用 Series。对于许多应用而言,Series 最实用的一个功能是它在算术运算中能自动对齐索引标签。

1
from pandas import Series, DataFrame

1.2.1. 由数组简单创建(默认索引)

1
2
obj = Series([1, 3, 5, 7]) 
obj

通过 pd.Series() 直接转换 Python 列表,默认生成从 0 开始的整数索引。

1.2.2. 由字典创建(自定义索引)

字典的键自动转为索引,值转为数据:

1
2
obj = Series({"a": 4, "b": 3, "c": 2, "d": 1})
obj

50%


通过to_dict的方法,Series也能转换回字典:

1
cprint("转换回字典:{}",obj.to_dict())

1.2.3. 自定义索引

通过 index 参数指定任意不可变对象作为索引:

1
2
obj = Series([90, 85, 92], index=["数学", "英语", "物理"], dtype="float64")
obj

50%

1.2.4. 创建带时间戳索引的Series

生成时间序列数据:

1
2
dates = pd.date_range("20230308", periods=4)
s = Series([100, 200, 300, 400], index=dates)


50%

1.2.5. 查询数组值和索引对象

可以通过Series的array和index属性获取其数组值和索引对象:

1
2
3
obj = Series([1, 3, 5, 7], index=["a","b","c","d"]) 
print("数组值:{}", obj.array)
print("索引对象:{}", obj.index)

与Numpy数组相比,可以通过索引的标签选取Series中的单个或一组值:

1
print(obj["a"])


1
2
3
4
5
6
7
8
obj["d"] = 6
print(obj[["a","c","d"]])

print(obj[obj>5])

print(obj * 2)

print(np.exp(obj))

50%


构建Series和Pandas时,所用到的任何数组或其他标签序列都会转换为索引对象:

1
2
3
4
5
6
7
obj = Series(np.arange(3), index=["a","b","c"])
index = obj.index
print("index:",index)
print("index[1:]",index[1:])

# 注意Index对象是不可变的,因此用户不能对其修改
index[1]="d" # TypeError

由于Index对象的不可变性,可以使索引对象在多个数据结构之前安全共享:

1
2
3
4
5
labels = pd.Index(np.arange(3))
print(labels)

obj = Series([1.5,-2.5,0],index=labels)
print(obj)

下面总结一下常用的索引的方法和属性:

方法/属性 描述 示例
append 连接额外的索引对象,生成一个新的索引。 new_index = index1.append(index2)
diff 计算索引的差集。 diff_index = index1.diff(index2)
intersection 计算索引的交集。 common_index = index1.intersection(index2)
union 计算索引的并集。 union_index = index1.union(index2)
isin 返回一个布尔数组,表示每个值是否包含在传递的集合中。 bool_array = index.isin(['a', 'b'])
delete 删除指定位置的元素,返回新的索引。 new_index = index.delete(0)
drop 删除传递的值,返回新的索引。 new_index = index.drop('a')
insert 在指定位置插入元素,返回新的索引。 new_index = index.insert(1, 'new_value')
is_monotonic 返回 True,如果索引是单调递增或递减的。 is_monotonic = index.is_monotonic
is_unique 返回 True,如果索引没有重复的值。 is_unique = index.is_unique

方法/属性 描述 示例
unique 返回索引的唯一值数组。 unique_values = index.unique()
reindex 根据新索引重新排列数据,缺失值用 NaN 填充。 new_series = series.reindex(new_index)
reset_index 重置索引为默认整数索引,原索引变为列。 df_reset = df.reset_index()
set_index 将某一列设置为索引。 df.set_index('column_name', inplace=True)
sort_values 对索引进行排序。 sorted_index = index.sort_values()
to_series 将索引转换为 Series index_series = index.to_series()
values 返回索引的 NumPy 数组。 index_values = index.values
name 获取或设置索引的名称。 index_name = index.name
shape 返回索引的形状(长度)。 index_shape = index.shape
size 返回索引的长度。 index_size = index.size

以下是一个与金融量化相关的代码示例,展示了如何使用 Pandas 计算股票的移动平均线(MA)和相对强弱指数(RSI):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd
import numpy as np

# 模拟股票价格数据
dates = pd.date_range("2025-01-01", periods=100)
prices = pd.Series(np.random.randint(100, 200, size=100), index=dates)

# 计算移动平均线 (MA)
ma_10 = prices.rolling(window=10).mean()
ma_20 = prices.rolling(window=20).mean()

# 计算相对强弱指数 (RSI)
delta = prices.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))


1
2
3
4
5
6
7
8
9
# 输出结果
result = pd.DataFrame({
    "Price": prices,
    "MA_10": ma_10,
    "MA_20": ma_20,
    "RSI": rsi
})

print(result.tail())