Skip to content

5.1 新增列与列重命名

在数据分析过程中,我们经常需要创建新的特征列或者对现有列进行重命名以提高可读性。pandas提供了非常灵活的方法来处理这些需求。

新增列的方法

第一要素:风格要保持一致

在pandas中新增列有多种方式,最常用的是直接赋值和使用assign方法。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
直接赋值新增列df['新列名'] = 值最简单的方式,值可以是标量、列表、Series或计算表达式
使用assign方法df.assign(新列名=值)返回新DataFrame,不修改原数据,支持链式操作
apply函数新增列df['新列名'] = df.apply(函数, axis=1)对每行应用函数,axis=1表示按行,axis=0表示按列

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建示例数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六'],
    '年龄': [25, 30, 35, 28],
    '工资': [8000, 12000, 15000, 9000]
}
df = pd.DataFrame(data)

# 方法1:直接赋值新增列 - 添加奖金列
df['奖金'] = df['工资'] * 0.1  # 奖金为工资的10%
print("直接赋值新增列:")
print(df)

# 方法2:使用assign方法 - 添加总收入列(不修改原df)
df_with_total = df.assign(总收入=df['工资'] + df['奖金'])
print("\n使用assign方法:")
print(df_with_total)

# 方法3:使用apply函数 - 根据年龄和工资计算综合评分
def calculate_score(row):
    """计算综合评分:年龄权重0.3,工资权重0.7"""
    return row['年龄'] * 0.3 + row['工资'] / 1000 * 0.7

df['综合评分'] = df.apply(calculate_score, axis=1)
print("\n使用apply函数新增列:")
print(df)

第四要素:注意事项

  • 直接赋值会修改原DataFrame,而assign方法返回新的DataFrame
  • 使用apply时要注意axis参数,axis=1表示按行处理,axis=0表示按列处理
  • 新增列的长度必须与DataFrame的行数匹配,否则会报错

新增列是数据分析中最基础的操作之一,无论是创建衍生特征还是计算指标,都离不开这个功能。掌握多种新增列的方法可以让你的代码更加灵活和高效。

列重命名的方法

第一要素:风格要保持一致

列重命名同样有多种方式,可以根据不同场景选择合适的方法。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
rename方法df.rename(columns={'旧名':'新名'})最常用的方法,columns参数传入字典映射
直接修改columns属性df.columns = ['新列名1', '新列名2', ...]一次性重命名所有列,需要提供完整的新列名列表
使用str方法批量重命名df.columns = df.columns.str.replace('old', 'new')适用于批量替换列名中的特定字符串

第三要素:使用示例

python
import pandas as pd

# 创建示例数据
data = {
    'name': ['张三', '李四', '王五'],
    'age': [25, 30, 35],
    'salary': [8000, 12000, 15000]
}
df = pd.DataFrame(data)

print("原始DataFrame:")
print(df)
print("原始列名:", df.columns.tolist())

# 方法1:使用rename方法重命名单个或多个列
df_renamed = df.rename(columns={
    'name': '姓名',
    'age': '年龄',
    'salary': '工资'
})
print("\n使用rename方法重命名后:")
print(df_renamed)

# 方法2:直接修改columns属性(重命名所有列)
df_copy = df.copy()  # 创建副本避免修改原数据
df_copy.columns = ['员工姓名', '员工年龄', '员工工资']
print("\n直接修改columns属性:")
print(df_copy)

# 方法3:使用str方法批量重命名(添加前缀)
df_prefixed = df.copy()
df_prefixed.columns = '员工_' + df_prefixed.columns
print("\n使用str方法批量添加前缀:")
print(df_prefixed)

# 方法4:使用rename配合函数进行条件重命名
df_func = df.rename(columns=lambda x: x.upper() if 'a' in x else x)
print("\n使用函数条件重命名:")
print(df_func)

第四要素:注意事项

  • rename方法默认返回新DataFrame,如果要修改原DataFrame需要设置inplace=True参数
  • 直接修改columns属性时,新列名的数量必须与原列数完全一致
  • 使用str方法时要注意原列名的数据类型必须是字符串类型

列重命名虽然看似简单,但在实际项目中非常重要。良好的列命名规范可以大大提高代码的可读性和维护性,特别是在团队协作的场景下。

5.2 行/列排序与索引重置

数据排序是数据分析中的常见需求,无论是按数值大小、时间顺序还是其他条件,合理的排序能让数据更有意义。

按列排序

第一要素:风格要保持一致

pandas提供了sort_values方法来进行基于列值的排序,这是最常用的排序方式。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
单列排序df.sort_values('列名')按指定列升序排序,默认ascending=True
多列排序df.sort_values(['列1', '列2'])按多个列排序,优先级从左到右
降序排序df.sort_values('列名', ascending=False)设置ascending=False进行降序排序
自定义排序df.sort_values('列名', key=函数)使用key参数自定义排序逻辑(pandas 1.1.0+)

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建示例数据
np.random.seed(42)  # 设置随机种子保证结果可重现
data = {
    '产品': ['A', 'B', 'C', 'D', 'E'],
    '销售额': [15000, 23000, 18000, 12000, 28000],
    '利润率': [0.15, 0.12, 0.18, 0.10, 0.14],
    '地区': ['北京', '上海', '广州', '深圳', '杭州']
}
df = pd.DataFrame(data)

print("原始数据:")
print(df)

# 方法1:按单列升序排序(销售额)
df_sorted_sales_asc = df.sort_values('销售额')
print("\n按销售额升序排序:")
print(df_sorted_sales_asc)

# 方法2:按单列降序排序(销售额)
df_sorted_sales_desc = df.sort_values('销售额', ascending=False)
print("\n按销售额降序排序:")
print(df_sorted_sales_desc)

# 方法3:多列排序(先按利润率降序,再按销售额降序)
df_multi_sort = df.sort_values(['利润率', '销售额'], ascending=[False, False])
print("\n多列排序(利润率降序,销售额降序):")
print(df_multi_sort)

# 方法4:处理缺失值的排序
df_with_nan = df.copy()
df_with_nan.loc[2, '销售额'] = np.nan  # 在第2行销售额设为NaN
print("\n包含缺失值的数据:")
print(df_with_nan)

# 默认NaN排在最后
df_nan_sorted = df_with_nan.sort_values('销售额', ascending=False)
print("\n包含缺失值的降序排序(NaN在最后):")
print(df_nan_sorted)

# 使用na_position参数控制NaN位置
df_nan_first = df_with_nan.sort_values('销售额', ascending=False, na_position='first')
print("\nNaN排在最前面:")
print(df_nan_first)

第四要素:注意事项

  • sort_values默认返回新的DataFrame,如需修改原数据需设置inplace=True
  • 多列排序时,ascending参数可以传入布尔值列表来分别控制各列的排序方向
  • 缺失值(NaN)默认排在最后,可通过na_position参数控制其位置
  • 字符串排序是按字典序进行的

按列排序是数据分析的基础操作,合理使用排序可以让重要的数据更容易被发现,比如找出销售额最高的产品或者利润率最低的项目。

按索引排序

第一要素:风格要保持一致

除了按列值排序,有时我们也需要按索引进行排序,这时使用sort_index方法。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
按行索引排序df.sort_index()按行索引升序排序
按列索引排序df.sort_index(axis=1)按列名升序排序,axis=1表示列方向
降序索引排序df.sort_index(ascending=False)降序排列索引
多级索引排序df.sort_index(level=0)对多级索引的指定级别进行排序

第三要素:使用示例

python
import pandas as pd

# 创建带有自定义索引的DataFrame
data = {
    '销售额': [15000, 23000, 18000, 12000],
    '利润率': [0.15, 0.12, 0.18, 0.10]
}
index = ['产品D', '产品A', '产品C', '产品B']  # 自定义行索引
df = pd.DataFrame(data, index=index)

print("原始数据(自定义索引):")
print(df)

# 方法1:按行索引排序
df_index_sorted = df.sort_index()
print("\n按行索引升序排序:")
print(df_index_sorted)

# 方法2:按列索引排序
df_columns_unsorted = df  # 调换列顺序
print("\n列顺序调换后的数据:")
print(df_columns_unsorted)

df_columns_sorted = df_columns_unsorted.sort_index(axis=1)
print("\n按列索引升序排序:")
print(df_columns_sorted)

# 方法3:创建多级索引并排序
arrays = [['Q1', 'Q1', 'Q2', 'Q2'], ['产品A', '产品B', '产品A', '产品B']]
multi_index = pd.MultiIndex.from_arrays(arrays, names=['季度', '产品'])
df_multi = pd.DataFrame({
    '销售额': [15000, 12000, 18000, 16000]
}, index=multi_index)

print("\n多级索引数据:")
print(df_multi)

# 按第一级索引(季度)排序
df_multi_sorted_level0 = df_multi.sort_index(level=0)
print("\n按季度排序:")
print(df_multi_sorted_level0)

# 按第二级索引(产品)排序
df_multi_sorted_level1 = df_multi.sort_index(level=1)
print("\n按产品排序:")
print(df_multi_sorted_level1)

第四要素:注意事项

  • sort_index默认按行索引排序(axis=0),按列排序需要设置axis=1
  • 多级索引排序时,level参数指定要排序的索引级别
  • 索引排序同样支持ascending和na_position参数

按索引排序在处理时间序列数据或需要按特定顺序展示数据时非常有用,特别是当索引本身包含有意义的信息时。

索引重置

第一要素:风格要保持一致

在进行各种操作后,DataFrame的索引可能会变得不连续或不符合需求,这时就需要重置索引。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
重置索引df.reset_index()将当前索引变为普通列,创建新的整数索引
丢弃原索引df.reset_index(drop=True)重置索引但不保留原索引作为列
重置多级索引df.reset_index(level=0)只重置多级索引中的指定级别

第三要素:使用示例

python
import pandas as pd

# 创建示例数据
data = {
    '产品': ['A', 'B', 'C', 'D'],
    '销售额': [15000, 23000, 18000, 12000]
}
df = pd.DataFrame(data)

# 先进行筛选操作,造成索引不连续
df_filtered = df[df['销售额'] > 15000]
print("筛选后的数据(索引不连续):")
print(df_filtered)
print("索引:", df_filtered.index.tolist())

# 方法1:重置索引(保留原索引作为列)
df_reset_with_old = df_filtered.reset_index()
print("\n重置索引(保留原索引):")
print(df_reset_with_old)

# 方法2:重置索引(丢弃原索引)
df_reset_drop = df_filtered.reset_index(drop=True)
print("\n重置索引(丢弃原索引):")
print(df_reset_drop)

# 方法3:处理带自定义索引的情况
df_custom_index = df.set_index('产品')
print("\n设置产品列为索引:")
print(df_custom_index)

df_reset_from_custom = df_custom_index.reset_index()
print("\n从自定义索引重置:")
print(df_reset_from_custom)

# 方法4:多级索引重置
arrays = [['Q1', 'Q1', 'Q2', 'Q2'], ['A', 'B', 'A', 'B']]
multi_index = pd.MultiIndex.from_arrays(arrays, names=['季度', '产品'])
df_multi = pd.DataFrame({'销售额': [15000, 12000, 18000, 16000]}, index=multi_index)
print("\n多级索引数据:")
print(df_multi)

# 只重置产品级别
df_reset_product = df_multi.reset_index(level='产品')
print("\n只重置产品级别索引:")
print(df_reset_product)

# 重置所有级别
df_reset_all = df_multi.reset_index()
print("\n重置所有索引级别:")
print(df_reset_all)

第四要素:注意事项

  • reset_index默认将原索引作为新列保存,如果不需要可以设置drop=True
  • 对于多级索引,可以通过level参数指定重置哪些级别
  • 重置索引后会创建从0开始的连续整数索引
  • 如果原DataFrame有重复索引,重置后会保留所有行

索引重置是数据清洗的重要步骤,特别是在进行了筛选、合并等操作后,连续的索引有助于后续的数据处理和分析。

5.3 数据分箱与离散化处理

在数据分析中,有时我们需要将连续的数值数据转换为离散的类别,这个过程称为分箱(binning)或离散化。这样做可以帮助我们更好地理解数据分布,或者满足某些算法对输入数据的要求。

等宽分箱

第一要素:风格要保持一致

等宽分箱是将数据范围均匀分割成若干个区间,每个区间的宽度相同。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
等宽分箱pd.cut(数据, bins=数量)将数据分成指定数量的等宽区间
自定义分箱边界pd.cut(数据, bins=[边界列表])使用自定义的边界进行分箱
分箱标签pd.cut(数据, bins=数量, labels=标签列表)为每个区间指定自定义标签
获取分箱信息pd.cut(数据, bins=数量, retbins=True)同时返回分箱结果和边界信息

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建示例数据:学生成绩
np.random.seed(42)
scores = np.random.randint(0, 101, 20)  # 20个0-100的随机成绩
df = pd.DataFrame({'学号': range(1, 21), '成绩': scores})

print("原始成绩数据:")
print(df.head(10))  # 显示前10行

# 方法1:等宽分箱(分成4个等级)
df['成绩等级_等宽'] = pd.cut(df['成绩'], bins=4)
print("\n等宽分箱结果(4个区间):")
print(df.head(10))

# 查看分箱的区间边界
grades_equal_width = pd.cut(df['成绩'], bins=4, retbins=True)
print("\n等宽分箱的边界:")
print(grades_equal_width[1])  # 第二个元素是边界数组

# 方法2:自定义分箱边界(按照常见的成绩等级)
custom_bins = [0, 60, 70, 80, 90, 100]
df['成绩等级_自定义'] = pd.cut(df['成绩'], bins=custom_bins)
print("\n自定义分箱结果:")
print(df.head(10))

# 方法3:使用自定义标签
grade_labels = ['不及格', '及格', '中等', '良好', '优秀']
df['成绩等级_标签'] = pd.cut(df['成绩'], bins=custom_bins, labels=grade_labels)
print("\n带自定义标签的分箱结果:")
print(df.head(10))

# 方法4:处理边界值
# right参数控制区间是否包含右边界,默认right=True
df['成绩等级_左闭'] = pd.cut(df['成绩'], bins=custom_bins, right=False)
print("\n左闭区间分箱(不包含右边界):")
sample_row = df[df['成绩'] == 60]  # 找一个边界值的例子
if not sample_row.empty:
    print(sample_row)

第四要素:注意事项

  • pd.cut默认创建左开右闭的区间(except the first interval which is closed on both sides)
  • bins参数可以是整数(等宽分箱)或边界列表(自定义分箱)
  • labels参数的长度应该比bins少1(因为n个边界产生n-1个区间)
  • 如果数据中有超出bins范围的值,会被标记为NaN

等宽分箱适合数据分布相对均匀的情况,但如果数据分布不均匀,可能会导致某些区间数据过多而其他区间数据过少。

等频分箱

第一要素:风格要保持一致

等频分箱(也称为分位数分箱)是将数据分成若干个区间,每个区间包含大致相同数量的数据点。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
等频分箱pd.qcut(数据, q=数量)将数据分成指定数量的等频区间
自定义分位数pd.qcut(数据, q=[分位数列表])使用自定义的分位数进行分箱
分箱标签pd.qcut(数据, q=数量, labels=标签列表)为每个分位数区间指定标签
处理重复值pd.qcut(数据, q=数量, duplicates='drop')处理分位数重复的情况

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建示例数据:收入数据(偏态分布)
np.random.seed(42)
# 创建偏态分布的数据,模拟真实收入情况
income_data = np.random.exponential(scale=50000, size=100)  # 指数分布
df_income = pd.DataFrame({'人员ID': range(1, 101), '年收入': income_data})

print("收入数据统计信息:")
print(df_income['年收入'].describe())

# 方法1:等频分箱(分成4个四分位数)
df_income['收入分组_等频'] = pd.qcut(df_income['年收入'], q=4)
print("\n等频分箱结果(4个分位数):")
print(df_income.head(10))

# 验证每个分组的数据量是否相等
print("\n各分组数据量:")
print(df_income['收入分组_等频'].value_counts().sort_index())

# 方法2:自定义分位数(十分位数)
decile_quantiles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
df_income['收入分组_十分位'] = pd.qcut(df_income['年收入'], q=decile_quantiles)
print("\n十分位数分箱结果:")
print(df_income.head(5))

# 方法3:使用自定义标签
income_labels = ['最低10%', '10-20%', '20-30%', '30-40%', '40-50%', 
                 '50-60%', '60-70%', '70-80%', '80-90%', '最高10%']
df_income['收入百分位'] = pd.qcut(df_income['年收入'], q=10, labels=income_labels)
print("\n带百分位标签的分箱结果:")
print(df_income.head(10))

# 方法4:处理重复值的情况
# 创建包含很多重复值的数据
repeated_data = [1, 1, 1, 1, 2, 2, 2, 3, 3, 4]
try:
    result = pd.qcut(repeated_data, q=4)
    print("\n重复值分箱结果:")
    print(result)
except ValueError as e:
    print(f"\n分箱错误: {e}")
    # 使用duplicates参数处理
    result_safe = pd.qcut(repeated_data, q=4, duplicates='drop')
    print("使用duplicates='drop'处理后:")
    print(result_safe)

第四要素:注意事项

  • pd.qcut基于样本分位数进行分箱,确保每个区间有大致相同的样本数量
  • 当数据中存在大量重复值时,可能无法创建指定数量的分箱,需要使用duplicates参数
  • duplicates='drop'会减少分箱数量,duplicates='raise'(默认)会抛出异常
  • 等频分箱对于偏态分布的数据特别有用,可以避免某些区间数据过少的问题

等频分箱在处理偏态分布数据时比等宽分箱更有效,因为它能确保每个分组都有足够的数据点进行分析。

分箱的应用场景

第一要素:风格要保持一致

分箱技术在实际数据分析中有多种应用场景,下面我们通过具体例子来展示。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
年龄分组分析pd.cut(年龄列, bins=年龄边界, labels=年龄标签)将连续年龄转换为年龄段进行分析
价格区间统计pd.qcut(价格列, q=分位数)按价格分位数分组统计销售情况
特征工程df['新特征'] = pd.cut(原始特征, bins=边界)为机器学习模型创建离散特征
异常值处理pd.cut(数据, bins=边界, include_lowest=True)将极端值归入特定区间

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建电商用户数据示例
np.random.seed(42)
user_data = {
    '用户ID': range(1, 1001),
    '年龄': np.random.randint(18, 80, 1000),
    '消费金额': np.random.exponential(scale=2000, size=1000),
    '购买次数': np.random.poisson(lam=5, size=1000)
}
df_users = pd.DataFrame(user_data)

print("用户数据概览:")
print(df_users.head())

# 应用场景1:年龄分组分析
age_bins = [18, 25, 35, 45, 55, 80]
age_labels = ['18-25岁', '26-35岁', '36-45岁', '46-55岁', '56岁以上']
df_users['年龄段'] = pd.cut(df_users['年龄'], bins=age_bins, labels=age_labels, right=False)

# 按年龄段统计平均消费
age_consumption = df_users.groupby('年龄段')['消费金额'].agg(['mean', 'count']).round(2)
print("\n各年龄段平均消费和用户数量:")
print(age_consumption)

# 应用场景2:消费金额分位数分组
df_users['消费分位'] = pd.qcut(df_users['消费金额'], q=5, labels=['最低20%', '20-40%', '40-60%', '60-80%', '最高20%'])

# 按消费分位统计购买次数
consumption_behavior = df_users.groupby('消费分位')['购买次数'].mean().round(2)
print("\n各消费分位用户的平均购买次数:")
print(consumption_behavior)

# 应用场景3:创建机器学习特征
# 将连续特征离散化作为分类特征
df_users['年龄分组'] = pd.cut(df_users['年龄'], bins=3, labels=['年轻', '中年', '老年'])
df_users['消费等级'] = pd.qcut(df_users['消费金额'], q=3, labels=['低', '中', '高'])

# 查看离散化后的特征
print("\n离散化特征示例:")
print(df_users.head(10))

# 应用场景4:处理异常值
# 找出消费金额的异常值并分组
consumption_stats = df_users['消费金额'].describe()
q1, q3 = consumption_stats['25%'], consumption_stats['75%']
iqr = q3 - q1
upper_bound = q3 + 1.5 * iqr

# 创建包含异常值的分箱
outlier_bins = [0, q1, q3, upper_bound, df_users['消费金额'].max()]
outlier_labels = ['低消费', '正常低', '正常高', '异常高']
df_users['消费异常分组'] = pd.cut(df_users['消费金额'], bins=outlier_bins, labels=outlier_labels, include_lowest=True)

# 统计异常值比例
outlier_count = (df_users['消费金额'] > upper_bound).sum()
print(f"\n异常值数量: {outlier_count}, 占总数据比例: {outlier_count/len(df_users)*100:.2f}%")

第四要素:注意事项

  • 分箱后的数据更适合进行分组统计和可视化分析
  • 在机器学习中,离散化可以减少异常值的影响,但可能丢失一些信息
  • 选择分箱方法时要考虑数据的分布特征和业务需求
  • 分箱边界的选择应该有业务意义,而不是纯粹的数学分割

分箱技术是连接连续数据和离散分析的桥梁,在实际项目中应用广泛。合理使用分箱可以让复杂的数据变得更加直观和易于理解。

5.4 日期时间类型处理与解析

在数据分析中,时间序列数据非常常见,正确处理日期时间类型对于时间相关的分析至关重要。pandas提供了强大的日期时间处理功能。

日期时间类型创建与转换

第一要素:风格要保持一致

pandas中的日期时间类型主要是datetime64[ns],可以通过多种方式创建和转换。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
字符串转日期pd.to_datetime(字符串)将字符串转换为datetime类型
指定格式转换pd.to_datetime(字符串, format='格式')按指定格式解析日期字符串
处理错误值pd.to_datetime(字符串, errors='coerce')errors参数控制错误处理方式
从组件创建pd.to_datetime({'year':年,'month':月,'day':日})从年月日等组件创建日期

第三要素:使用示例

python
import pandas as pd
import numpy as np

# 创建包含各种日期格式的示例数据
date_strings = [
    '2023-01-15',
    '15/01/2023',
    '2023年1月15日',
    'Jan 15, 2023',
    '2023-01-15 14:30:00',
    'invalid_date'  # 无效日期用于演示错误处理
]

df_dates = pd.DataFrame({'原始日期': date_strings})

print("原始日期字符串:")
print(df_dates)

# 方法1:自动推断格式转换
df_dates['自动转换'] = pd.to_datetime(df_dates['原始日期'], errors='coerce')
print("\n自动推断格式转换(errors='coerce'):")
print(df_dates)

# 方法2:指定格式转换
# ISO格式
iso_dates = ['2023-01-15', '2023-02-20', '2023-03-25']
df_iso = pd.DataFrame({'ISO日期': iso_dates})
df_iso['转换后'] = pd.to_datetime(df_iso['ISO日期'], format='%Y-%m-%d')
print("\nISO格式指定转换:")
print(df_iso)

# 欧洲格式(日/月/年)
euro_dates = ['15/01/2023', '20/02/2023', '25/03/2023']
df_euro = pd.DataFrame({'欧洲日期': euro_dates})
df_euro['转换后'] = pd.to_datetime(df_euro['欧洲日期'], format='%d/%m/%Y')
print("\n欧洲格式指定转换:")
print(df_euro)

# 中文格式
chinese_dates = ['2023年1月15日', '2023年2月20日', '2023年3月25日']
df_chinese = pd.DataFrame({'中文日期': chinese_dates})
df_chinese['转换后'] = pd.to_datetime(df_chinese['中文日期'], format='%Y年%m月%d日')
print("\n中文格式指定转换:")
print(df_chinese)

# 方法3:从日期组件创建
date_components = {
    'year': [2023, 2023, 2023],
    'month': [1, 2, 3],
    'day': [15, 20, 25],
    'hour': [14, 15, 16]
}
df_components = pd.DataFrame(date_components)
df_components['完整日期'] = pd.to_datetime(df_components)
print("\n从日期组件创建:")
print(df_components)

# 方法4:处理Unix时间戳
timestamps = [1673769600, 1676851200, 1679932800]  # Unix时间戳
df_timestamps = pd.DataFrame({'时间戳': timestamps})
df_timestamps['转换日期'] = pd.to_datetime(df_timestamps['时间戳'], unit='s')
print("\nUnix时间戳转换:")
print(df_timestamps)

第四要素:注意事项

  • pd.to_datetime的format参数使用标准的strftime格式代码
  • errors参数可选值:'raise'(默认,抛出异常)、'coerce'(转换失败设为NaT)、'ignore'(保持原值)
  • 自动推断格式虽然方便,但对于模糊格式(如01/02/2023)可能产生意外结果
  • Unix时间戳的unit参数指定时间单位('s'秒、'ms'毫秒等)

正确的时间格式转换是时间序列分析的第一步,只有确保日期时间类型正确,后续的时间操作才能正常进行。

日期时间属性提取

第一要素:风格要保持一致

一旦有了正确的datetime类型,就可以轻松提取各种时间属性,如年、月、日、星期等。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
提取年份df['日期列'].dt.year提取年份(整数)
提取月份df['日期列'].dt.month提取月份(1-12)
提取星期df['日期列'].dt.dayofweek提取星期(0=周一,6=周日)
提取小时df['日期列'].dt.hour提取小时(0-23)
判断工作日df['日期列'].dt.weekday < 5判断是否为工作日
提取季度df['日期列'].dt.quarter提取季度(1-4)

第三要素:使用示例

python
import pandas as pd

# 创建包含日期时间的示例数据
dates = pd.date_range('2023-01-01', periods=10, freq='2D')  # 每2天一个日期
sales = [1000, 1200, 800, 1500, 900, 1100, 1300, 950, 1400, 1050]
df_sales = pd.DataFrame({'销售日期': dates, '销售额': sales})

print("销售数据:")
print(df_sales)

# 提取基本时间属性
df_sales['年份'] = df_sales['销售日期'].dt.year
df_sales['月份'] = df_sales['销售日期'].dt.month
df_sales['日'] = df_sales['销售日期'].dt.day
df_sales['星期'] = df_sales['销售日期'].dt.dayofweek
df_sales['小时'] = df_sales['销售日期'].dt.hour  # 这里都是0,因为没有时间部分

print("\n提取基本时间属性:")
print(df_sales)

# 提取更多时间属性
df_sales['季度'] = df_sales['销售日期'].dt.quarter
df_sales['年中的第几天'] = df_sales['销售日期'].dt.dayofyear
df_sales['月中的第几周'] = df_sales['销售日期'].dt.weekofyear
df_sales['是否月末'] = df_sales['销售日期'].dt.is_month_end
df_sales['是否月初'] = df_sales['销售日期'].dt.is_month_start

print("\n提取更多时间属性:")
print(df_sales)

# 创建星期标签
weekday_names = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
df_sales['星期名称'] = df_sales['星期'].map(lambda x: weekday_names[x])

# 判断工作日和周末
df_sales['是否工作日'] = df_sales['星期'] < 5
df_sales['是否周末'] = df_sales['星期'] >= 5

print("\n星期相关分析:")
print(df_sales)

# 按月份和星期分析销售额
monthly_sales = df_sales.groupby('月份')['销售额'].sum()
weekday_sales = df_sales.groupby('星期名称')['销售额'].mean()

print("\n按月份汇总销售额:")
print(monthly_sales)
print("\n按星期平均销售额:")
print(weekday_sales.round(2))

第四要素:注意事项

  • 所有dt属性都要求Series的数据类型是datetime64[ns]
  • dayofweek返回0-6(周一到周日),而有些系统使用1-7(周日到周六)
  • is_month_end和is_month_start返回布尔值
  • weekofyear在不同版本的pandas中可能有不同的实现

时间属性提取让我们可以从多个维度分析时间序列数据,比如分析季节性趋势、工作日效应等,这些都是业务分析中的重要洞察。

日期时间运算与偏移

第一要素:风格要保持一致

pandas支持丰富的日期时间运算,包括加减时间偏移、计算时间差等。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
加减天数日期列 + pd.Timedelta(days=5)添加或减去指定天数
日期偏移日期列 + pd.DateOffset(months=1)添加月份、年份等复杂偏移
计算时间差(日期1 - 日期2).dt.days计算两个日期之间的天数差
移动到月末日期列 + pd.offsets.MonthEnd(0)将日期移动到当月最后一天
日期范围pd.date_range(start, end, freq='D')创建日期范围

第三要素:使用示例

python
import pandas as pd

# 创建订单数据示例
order_dates = pd.to_datetime(['2023-01-15', '2023-02-20', '2023-03-10', '2023-04-05'])
delivery_dates = pd.to_datetime(['2023-01-20', '2023-02-25', '2023-03-15', '2023-04-12'])
df_orders = pd.DataFrame({
    '订单日期': order_dates,
    '发货日期': delivery_dates
})

print("订单数据:")
print(df_orders)

# 方法1:计算配送时间(时间差)
df_orders['配送天数'] = (df_orders['发货日期'] - df_orders['订单日期']).dt.days
print("\n计算配送天数:")
print(df_orders)

# 方法2:添加时间偏移
# 添加7天(一周后)
df_orders['一周后'] = df_orders['订单日期'] + pd.Timedelta(days=7)
# 添加1个月
df_orders['一月后'] = df_orders['订单日期'] + pd.DateOffset(months=1)
# 添加2小时(如果有时间部分)
df_orders['两小时后'] = df_orders['订单日期'] + pd.Timedelta(hours=2)

print("\n添加时间偏移:")
print(df_orders)

# 方法3:移动到特定日期
# 移动到当月最后一天
df_orders['月末'] = df_orders['订单日期'] + pd.offsets.MonthEnd(0)
# 移动到下个月第一天
df_orders['下月首日'] = df_orders['订单日期'] + pd.offsets.MonthBegin(1)
# 移动到下一个周五
df_orders['下一个周五'] = df_orders['订单日期'] + pd.offsets.WeekOfMonth(week=0, weekday=4)

print("\n移动到特定日期:")
print(df_orders)

# 方法4:创建日期范围用于分析
# 创建2023年全年的工作日
workdays_2023 = pd.date_range('2023-01-01', '2023-12-31', freq='B')  # 'B'表示工作日
print(f"\n2023年工作日总数: {len(workdays_2023)}")

# 创建每季度第一天
quarter_starts = pd.date_range('2023-01-01', '2023-12-31', freq='QS')  # 'QS'表示季度开始
print("2023年各季度开始日期:")
print(quarter_starts)

# 方法5:处理节假日(简单示例)
# 假设我们知道一些节假日
holidays = pd.to_datetime(['2023-01-01', '2023-05-01', '2023-10-01'])
df_orders['是否节假日下单'] = df_orders['订单日期'].isin(holidays)
print("\n节假日下单标记:")
print(df_orders)

第四要素:注意事项

  • Timedelta用于固定时间间隔(天、小时、分钟等)
  • DateOffset用于日历相关的偏移(月、年等,考虑月份天数差异)
  • 时间差计算的结果是Timedelta类型,需要用.dt.days等属性提取数值
  • 频率字符串:'D'=天,'B'=工作日,'W'=周,'M'=月末,'MS'=月初,'Q'=季度末,'QS'=季度初
  • 复杂的节假日处理建议使用专门的库如holidays

日期时间运算是时间序列分析的核心能力,无论是计算业务指标(如配送时效)、创建预测模型的时间特征,还是进行周期性分析,都离不开这些操作。

时区处理

第一要素:风格要保持一致

在处理跨时区的数据时,正确处理时区信息非常重要,pandas提供了完整的时区支持。

第二要素:实例方法表格(三列结构)

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
本地化时区日期列.dt.tz_localize('时区')为无时区的日期时间添加时区信息
转换时区日期列.dt.tz_convert('目标时区')将日期时间转换到目标时区
移除时区日期列.dt.tz_localize(None)移除时区信息
获取可用时区pytz.all_timezones获取所有可用的时区列表(需要pytz库)

第三要素:使用示例

python
import pandas as pd
import pytz  # 需要安装pytz库

# 创建无时区的日期时间数据
timestamps = pd.date_range('2023-01-01 09:00:00', periods=5, freq='6H')
df_timezone = pd.DataFrame({'本地时间': timestamps})

print("原始无时区数据:")
print(df_timezone)

# 方法1:本地化时区(假设原始数据是北京时间)
df_timezone['北京时间'] = df_timezone['本地时间'].dt.tz_localize('Asia/Shanghai')
print("\n本地化为北京时间:")
print(df_timezone['北京时间'])

# 方法2:转换到其他时区
df_timezone['纽约时间'] = df_timezone['北京时间'].dt.tz_convert('America/New_York')
df_timezone['伦敦时间'] = df_timezone['北京时间'].dt.tz_convert('Europe/London')
df_timezone['东京时间'] = df_timezone['北京时间'].dt.tz_convert('Asia/Tokyo')

print("\n转换到不同时区:")
print(df_timezone)

# 方法3:比较不同时区的同一时刻
# 创建一个具体的时刻
specific_moment = pd.Timestamp('2023-07-15 12:00:00').tz_localize('Asia/Shanghai')
print(f"\n北京时间: {specific_moment}")
print(f"纽约时间: {specific_moment.tz_convert('America/New_York')}")
print(f"伦敦时间: {specific_moment.tz_convert('Europe/London')}")

# 方法4:处理夏令时
# 美国在3月第二个周日开始夏令时,11月第一个周日结束
summer_date = pd.Timestamp('2023-07-15 12:00:00').tz_localize('America/New_York')
winter_date = pd.Timestamp('2023-01-15 12:00:00').tz_localize('America/New_York')

print(f"\n纽约夏季时间(夏令时): {summer_date}")
print(f"对应的UTC时间: {summer_date.tz_convert('UTC')}")
print(f"纽约冬季时间(标准时间): {winter_date}")
print(f"对应的UTC时间: {winter_date.tz_convert('UTC')}")

# 方法5:移除时区信息(谨慎使用)
df_timezone['无时区时间'] = df_timezone['北京时间'].dt.tz_localize(None)
print("\n移除时区信息后:")
print(df_timezone['无时区时间'])

# 查看可用的时区(显示部分)
print(f"\n可用时区数量: {len(pytz.all_timezones)}")
print("部分中国相关时区:")
china_zones = [zone for zone in pytz.all_timezones if 'China' in zone or 'Shanghai' in zone or 'Hong_Kong' in zone]
print(china_zones)

第四要素:注意事项

  • 时区处理需要pytz库支持
  • tz_localize只能用于无时区的datetime数据
  • tz_convert只能用于有时区的datetime数据
  • 夏令时转换会自动处理时间跳变
  • 移除时区信息(tz_localize(None))会丢失时区数据,谨慎使用
  • 常用时区:'UTC', 'Asia/Shanghai', 'America/New_York', 'Europe/London'

时区处理在国际化业务中非常重要,正确的时区处理可以避免时间计算错误,确保全球用户看到的时间信息准确一致。