Pandas 入门教程

31 阅读23分钟

Pandas 入门教程

目录


什么是 Pandas

Pandas 是一个强大的 Python 数据分析库,建立在 NumPy 之上,提供了高性能、易用的数据结构和数据分析工具。

核心特点

  • DataFrame:类似 Excel 表格的二维数据结构
  • Series:一维标记数组
  • 数据读写:支持 CSV、Excel、SQL、JSON 等多种格式
  • 数据清洗:处理缺失值、重复值、异常值
  • 数据转换:重塑、透视、合并、分组
  • 时间序列:强大的日期和时间处理功能
  • 集成生态:与 NumPy、Matplotlib、Scikit-learn 无缝集成

Pandas vs Excel

特性ExcelPandas
数据量有限(约100万行)几乎无限(受内存限制)
自动化手动操作可编程自动化
可重复性
版本控制困难容易
复杂分析困难简单
学习曲线中等

安装 Pandas

方法一:通过 Anaconda 安装

Pandas 已预装在 Anaconda 中。

# 验证安装
python -c "import pandas; print(pandas.__version__)"

方法二:通过 pip 安装

pip install pandas

导入 Pandas

import pandas as pd
import numpy as np

# 查看版本
print(pd.__version__)

约定俗成:始终使用 import pandas as pd 作为标准导入方式。


核心数据结构

Pandas 有两个核心数据结构:SeriesDataFrame

Series(一维数组)

Series 是带标签的一维数组,可以存储任何数据类型。

import pandas as pd
import numpy as np

# 从列表创建
s = pd.Series([1, 3, 5, np.nan, 6, 8])
print(s)
# 0    1.0
# 1    3.0
# 2    5.0
# 3    NaN
# 4    6.0
# 5    8.0
# dtype: float64

# 指定索引
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(s)
# a    10
# b    20
# c    30
# d    40
# dtype: int64

# 从字典创建
data = {'apple': 3, 'banana': 5, 'orange': 2}
s = pd.Series(data)
print(s)
# apple     3
# banana    5
# orange    2
# dtype: int64

# Series 属性
print(s.index)    # Index(['apple', 'banana', 'orange'], dtype='object')
print(s.values)   # array([3, 5, 2])
print(s.dtype)    # int64
print(s.name)     # None

DataFrame(二维表格)

DataFrame 是带标签的二维数据结构,类似电子表格或 SQL 表。

# 从字典创建
data = {
    'Name': ['Alice', 'Bob', 'Charlie', 'David'],
    'Age': [25, 30, 35, 40],
    'City': ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen'],
    'Salary': [8000, 12000, 15000, 20000]
}
df = pd.DataFrame(data)
print(df)
#       Name  Age       City  Salary
# 0    Alice   25    Beijing    8000
# 1      Bob   30   Shanghai   12000
# 2  Charlie   35  Guangzhou   15000
# 3    David   40   Shenzhen   20000

# 从列表的列表创建
data = [
    ['Alice', 25, 'Beijing'],
    ['Bob', 30, 'Shanghai'],
    ['Charlie', 35, 'Guangzhou']
]
df = pd.DataFrame(data, columns=['Name', 'Age', 'City'])

# 从 NumPy 数组创建
arr = np.random.randn(5, 3)
df = pd.DataFrame(arr, columns=['A', 'B', 'C'])

# 从 Series 字典创建
data = {
    'one': pd.Series([1, 2, 3], index=['a', 'b', 'c']),
    'two': pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
}
df = pd.DataFrame(data)
print(df)
#    one  two
# a  1.0    1
# b  2.0    2
# c  3.0    3
# d  NaN    4

# DataFrame 属性
print(df.shape)     # (行数, 列数)
print(df.columns)   # 列名
print(df.index)     # 索引
print(df.dtypes)    # 每列的数据类型
print(df.values)    # NumPy 数组

数据读取与写入

读取数据

import pandas as pd

# 读取 CSV 文件
df = pd.read_csv('data.csv')
df = pd.read_csv('data.csv', encoding='utf-8')  # 指定编码
df = pd.read_csv('data.csv', sep='\t')          # 制表符分隔
df = pd.read_csv('data.csv', header=0)           # 指定表头行
df = pd.read_csv('data.csv', index_col=0)        # 指定索引列
df = pd.read_csv('data.csv', usecols=['A', 'B']) # 只读取指定列
df = pd.read_csv('data.csv', nrows=100)          # 只读取前100行

# 读取 Excel 文件
df = pd.read_excel('data.xlsx')
df = pd.read_excel('data.xlsx', sheet_name='Sheet1')
df = pd.read_excel('data.xlsx', sheet_name=0)

# 需要安装 openpyxl
# pip install openpyxl

# 读取 JSON 文件
df = pd.read_json('data.json')

# 读取 SQL 数据库
import sqlite3
conn = sqlite3.connect('database.db')
df = pd.read_sql_query('SELECT * FROM table_name', conn)
conn.close()

# 读取 HTML 表格
df = pd.read_html('https://example.com/table.html')[0]

# 读取剪贴板
df = pd.read_clipboard()

写入数据

# 写入 CSV
df.to_csv('output.csv', index=False)              # 不保存索引
df.to_csv('output.csv', encoding='utf-8-sig')     # Excel 兼容编码

# 写入 Excel
df.to_excel('output.xlsx', index=False, sheet_name='Sheet1')

# 写入 JSON
df.to_json('output.json', orient='records')

# 写入 SQL
df.to_sql('table_name', conn, if_exists='replace', index=False)

# 写入剪贴板
df.to_clipboard(index=False)

读取参数详解

# read_csv 常用参数
df = pd.read_csv(
    'data.csv',
    sep=',',              # 分隔符
    header=0,             # 表头行号
    index_col=None,       # 索引列
    usecols=None,         # 读取的列
    dtype=None,           # 数据类型
    na_values=['NA', ''], # 缺失值标记
    parse_dates=False,    # 解析日期
    encoding='utf-8',     # 编码
    nrows=None,           # 读取行数
    skiprows=None,        # 跳过的行
    chunksize=None        # 分块大小
)

数据查看与探索

import pandas as pd
import numpy as np

# 创建示例数据
df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'Age': [25, 30, 35, 40, 45],
    'City': ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Hangzhou'],
    'Salary': [8000, 12000, 15000, 20000, 18000],
    'Department': ['IT', 'HR', 'IT', 'Finance', 'HR']
})

基本信息查看

# 查看前几行
print(df.head())        # 前5行
print(df.head(3))       # 前3行

# 查看后几行
print(df.tail())        # 后5行
print(df.tail(2))       # 后2行

# 查看形状
print(df.shape)         # (5, 5)

# 查看列名
print(df.columns)       # Index(['Name', 'Age', 'City', 'Salary', 'Department'])

# 查看索引
print(df.index)         # RangeIndex(start=0, stop=5, step=1)

# 查看数据类型
print(df.dtypes)
# Name          object
# Age            int64
# City          object
# Salary         int64
# Department    object
# dtype: object

# 查看详细信息
print(df.info())
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 5 entries, 0 to 4
# Data columns (total 5 columns):
#  #   Column      Non-Null Count  Dtype
# ---  ------      --------------  -----
#  0   Name        5 non-null      object
#  1   Age         5 non-null      int64
#  2   City        5 non-null      object
#  3   Salary      5 non-null      int64
#  4   Department  5 non-null      object
# dtypes: int64(2), object(3)
# memory usage: 328.0+ bytes

# 统计摘要
print(df.describe())
#             Age       Salary
# count   5.00000     5.000000
# mean   35.00000  14600.000000
# std    7.90569   5458.937626
# min    25.00000   8000.000000
# 25%    30.00000  12000.000000
# 50%    35.00000  15000.000000
# 75%    40.00000  18000.000000
# max    45.00000  20000.000000

# 包括所有列的统计
print(df.describe(include='all'))

# 唯一值
print(df['City'].unique())
# ['Beijing' 'Shanghai' 'Guangzhou' 'Shenzhen' 'Hangzhou']

# 唯一值数量
print(df['City'].nunique())  # 5

# 值计数
print(df['Department'].value_counts())
# IT       2
# HR       2
# Finance  1
# Name: Department, dtype: int64

数据选择与索引

列选择

# 选择单列(返回 Series)
name_series = df['Name']
age_series = df['Age']

# 选择多列(返回 DataFrame)
subset = df[['Name', 'Age']]
subset = df[['Name', 'Age', 'Salary']]

# 使用 loc 选择列
df.loc[:, 'Name']
df.loc[:, ['Name', 'Age']]

行选择

# 按位置选择(iloc)
print(df.iloc[0])      # 第一行
print(df.iloc[0:3])    # 前3行
print(df.iloc[-1])     # 最后一行

# 按标签选择(loc)
df_indexed = df.set_index('Name')
print(df_indexed.loc['Alice'])

# 布尔索引
young = df[df['Age'] < 35]
high_salary = df[df['Salary'] > 15000]

loc 和 iloc 详解

# iloc - 基于位置的索引
df.iloc[0]            # 第一行
df.iloc[0:3]          # 第0-2行
df.iloc[:, 0]         # 第一列
df.iloc[0:3, 0:2]     # 前3行,前2列
df.iloc[[0, 2, 4]]    # 指定行

# loc - 基于标签的索引
df.loc[0]             # 索引为0的行
df.loc[0:2]           # 索引0-2的行(包含2)
df.loc[:, 'Name']     # Name列
df.loc[0:2, 'Name':'Age']  # 指定行列范围

# 条件选择
df.loc[df['Age'] > 30]
df.loc[df['Department'] == 'IT', ['Name', 'Salary']]

条件选择

# 单条件
df[df['Age'] > 30]
df[df['City'] == 'Beijing']

# 多条件(& 且,| 或,~ 非)
df[(df['Age'] > 30) & (df['Salary'] > 15000)]
df[(df['Department'] == 'IT') | (df['Department'] == 'HR')]
df[~(df['Age'] < 30)]  # 年龄不小于30

# isin
cities = ['Beijing', 'Shanghai']
df[df['City'].isin(cities)]

# between
df[df['Age'].between(25, 35)]

# str 方法
df[df['Name'].str.startswith('A')]
df[df['City'].str.contains('ang')]

query 方法

# 使用 query 进行条件查询
df.query('Age > 30')
df.query('Age > 30 and Salary > 15000')
df.query('Department in ["IT", "HR"]')
df.query('City == "Beijing" or City == "Shanghai"')

# 使用变量
min_age = 30
df.query('Age > @min_age')

数据清洗

处理缺失值

import pandas as pd
import numpy as np

# 创建含缺失值的数据
df = pd.DataFrame({
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 2, 3, 4, 5],
    'C': [1, 2, 3, np.nan, 5],
    'D': [1, 2, 3, 4, 5]
})

# 检测缺失值
print(df.isnull())       # 缺失值为 True
print(df.notnull())      # 非缺失值为 True
print(df.isnull().sum()) # 每列缺失值数量

# 删除缺失值
df_drop = df.dropna()                    # 删除任何含缺失值的行
df_drop = df.dropna(axis=1)              # 删除任何含缺失值的列
df_drop = df.dropna(how='all')           # 删除全为缺失值的行
df_drop = df.dropna(subset=['A', 'B'])   # 删除指定列含缺失值的行
df_drop = df.dropna(thresh=3)            # 至少3个非缺失值才保留

# 填充缺失值
df_filled = df.fillna(0)                 # 用0填充
df_filled = df.fillna(method='ffill')    # 向前填充
df_filled = df.fillna(method='bfill')    # 向后填充
df_filled = df.fillna(df.mean())         # 用均值填充
df_filled = df['A'].fillna(df['A'].median())  # 用中位数填充

# 插值
df_interpolated = df.interpolate()

# 判断是否有缺失值
print(df.isnull().any().any())  # True/False

处理重复值

# 创建含重复值的数据
df = pd.DataFrame({
    'A': [1, 2, 2, 3, 4, 4],
    'B': ['a', 'b', 'b', 'c', 'd', 'd']
})

# 检测重复值
print(df.duplicated())
# 0    False
# 1    False
# 2     True
# 3    False
# 4    False
# 5     True

# 删除重复值
df_unique = df.drop_duplicates()
df_unique = df.drop_duplicates(subset=['A'])  # 基于指定列
df_unique = df.drop_duplicates(keep='last')   # 保留最后一个
df_unique = df.drop_duplicates(keep=False)    # 删除所有重复

# 统计重复数量
print(df.duplicated().sum())  # 2

数据类型转换

# 查看数据类型
print(df.dtypes)

# 转换数据类型
df['Age'] = df['Age'].astype(int)
df['Salary'] = df['Salary'].astype(float)
df['Date'] = pd.to_datetime(df['Date'])
df['Category'] = df['Category'].astype('category')

# 转换多列
df = df.astype({'Age': int, 'Salary': float})

# 数值转字符串
df['Age_str'] = df['Age'].astype(str)

# 字符串转数值
df['Value'] = pd.to_numeric(df['Value'], errors='coerce')

# 分类类型(节省内存)
df['Department'] = df['Department'].astype('category')
print(df['Department'].cat.categories)

字符串处理

df = pd.DataFrame({
    'Name': ['alice', 'BOB', 'Charlie', 'david'],
    'Email': ['alice@example.com', 'bob@test.com',
              'charlie@example.com', 'david@test.com']
})

# 转换为小写/大写
df['Name_lower'] = df['Name'].str.lower()
df['Name_upper'] = df['Name'].str.upper()
df['Name_title'] = df['Name'].str.title()

# 去除空格
df['Name_strip'] = df['Name'].str.strip()

# 替换
df['Name_replaced'] = df['Name'].str.replace('a', 'A')

# 分割
df[['First', 'Last']] = df['Name'].str.split(' ', expand=True)

# 提取
df['Domain'] = df['Email'].str.extract(r'@(.+)')

# 包含判断
mask = df['Email'].str.contains('example')
df_example = df[mask]

# 长度
df['Name_length'] = df['Name'].str.len()

# 开头/结尾判断
df['StartsWith_a'] = df['Name'].str.startswith('a')
df['EndsWith_e'] = df['Name'].str.endswith('e')

重命名

# 重命名列
df = df.rename(columns={'old_name': 'new_name'})
df = df.rename(columns={'A': 'Column_A', 'B': 'Column_B'})

# 重命名索引
df = df.rename(index={0: 'first', 1: 'second'})

# 重命名所有列
df.columns = ['Col1', 'Col2', 'Col3']

# 使用函数重命名
df = df.rename(columns=str.lower)
df = df.rename(columns=lambda x: x.strip())

数据转换

添加新列

df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'Salary': [8000, 12000, 15000]
})

# 直接赋值
df['Bonus'] = df['Salary'] * 0.1

# 基于条件
df['Level'] = np.where(df['Salary'] > 10000, 'High', 'Low')

# 使用 apply
def age_group(age):
    if age < 30:
        return 'Young'
    elif age < 40:
        return 'Middle'
    else:
        return 'Senior'

df['Age_Group'] = df['Age'].apply(age_group)

# 使用 map
level_map = {8000: 'Junior', 12000: 'Mid', 15000: 'Senior'}
df['Level'] = df['Salary'].map(level_map)

# 使用 assign(返回新 DataFrame)
df_new = df.assign(
    Bonus=df['Salary'] * 0.1,
    Tax=df['Salary'] * 0.2
)

apply 函数

# Series 的 apply
df['Age_squared'] = df['Age'].apply(lambda x: x ** 2)

# DataFrame 的 apply(按列)
df[['Age', 'Salary']].apply(np.mean)

# DataFrame 的 apply(按行)
def custom_func(row):
    return row['Salary'] / row['Age']

df['Salary_per_Age'] = df.apply(custom_func, axis=1)

# 带额外参数
def multiply(x, factor):
    return x * factor

df['Salary_doubled'] = df['Salary'].apply(multiply, factor=2)

map 和 replace

# map - Series 专用
df = pd.DataFrame({'City': ['Beijing', 'Shanghai', 'Guangzhou']})
city_code = {'Beijing': 'BJ', 'Shanghai': 'SH', 'Guangzhou': 'GZ'}
df['City_Code'] = df['City'].map(city_code)

# replace - 替换值
df = pd.DataFrame({'Status': [1, 0, 1, 0, 1]})
df['Status'] = df['Status'].replace({1: 'Active', 0: 'Inactive'})

# 替换多个值
df = df.replace({'A': 1, 'B': 2, 'C': 3})

数据离散化

# cut - 等宽分箱
ages = [20, 25, 30, 35, 40, 45, 50]
bins = [0, 30, 40, 50, 60]
labels = ['Young', 'Middle', 'Old', 'Senior']
age_groups = pd.cut(ages, bins=bins, labels=labels)
print(age_groups)

# qcut - 等频分箱
scores = np.random.randint(0, 100, 100)
quantiles = pd.qcut(scores, q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
print(quantiles.value_counts())

数据筛选与查询

基本筛选

df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'Age': [25, 30, 35, 40, 45],
    'Salary': [8000, 12000, 15000, 20000, 18000],
    'Department': ['IT', 'HR', 'IT', 'Finance', 'HR']
})

# 单条件筛选
it_dept = df[df['Department'] == 'IT']
high_salary = df[df['Salary'] > 15000]

# 多条件筛选
result = df[(df['Age'] > 30) & (df['Salary'] > 15000)]
result = df[(df['Department'] == 'IT') | (df['Department'] == 'HR')]

# 取反
result = df[~(df['Age'] < 30)]

nlargest 和 nsmallest

# 最大的 N 个值
top3_salary = df.nlargest(3, 'Salary')
top2_age = df.nlargest(2, 'Age')

# 最小的 N 个值
bottom3_salary = df.nsmallest(3, 'Salary')

sample 随机采样

# 随机抽取
sample = df.sample(n=3)           # 抽取3行
sample = df.sample(frac=0.5)      # 抽取50%
sample = df.sample(n=3, random_state=42)  # 固定随机种子

# 加权采样
weights = [0.1, 0.1, 0.1, 0.5, 0.2]
sample = df.sample(n=2, weights=weights)

数据排序

df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'David'],
    'Age': [25, 30, 35, 40],
    'Salary': [8000, 12000, 15000, 20000]
})

# 按单列排序
df_sorted = df.sort_values('Age')
df_sorted = df.sort_values('Age', ascending=False)  # 降序

# 按多列排序
df_sorted = df.sort_values(['Department', 'Salary'],
                           ascending=[True, False])

# 按索引排序
df_sorted = df.sort_index()
df_sorted = df.sort_index(ascending=False)

# 原地排序
df.sort_values('Age', inplace=True)

# 获取排序后的索引
sorted_indices = df['Salary'].argsort()
df_sorted = df.iloc[sorted_indices]

数据统计分析

基本统计

df = pd.DataFrame({
    'A': [1, 2, 3, 4, 5],
    'B': [10, 20, 30, 40, 50],
    'C': [100, 200, 300, 400, 500]
})

# 求和
print(df.sum())
print(df.sum(axis=1))  # 按行求和

# 平均值
print(df.mean())

# 中位数
print(df.median())

# 标准差
print(df.std())

# 方差
print(df.var())

# 最小值/最大值
print(df.min())
print(df.max())

# 极差
print(df.max() - df.min())

# 众数
print(df.mode())

# 分位数
print(df.quantile(0.25))  # 25% 分位数
print(df.quantile([0.25, 0.5, 0.75]))

# 描述性统计
print(df.describe())

# 相关性
print(df.corr())

# 协方差
print(df.cov())

# 计数
print(df.count())
print(df['A'].value_counts())

分组统计

df = pd.DataFrame({
    'Department': ['IT', 'HR', 'IT', 'Finance', 'HR', 'IT'],
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
    'Salary': [8000, 12000, 15000, 20000, 18000, 22000],
    'Age': [25, 30, 35, 40, 45, 28]
})

# 按部门分组
grouped = df.groupby('Department')

# 分组统计
print(grouped.mean())
print(grouped.sum())
print(grouped.count())
print(grouped.std())

# 特定列的统计
print(grouped['Salary'].mean())
print(grouped[['Salary', 'Age']].mean())

# 多个统计量
print(grouped['Salary'].agg(['mean', 'sum', 'count']))

# 自定义统计
print(grouped['Salary'].agg(lambda x: x.max() - x.min()))

分组聚合

groupby 基础

df = pd.DataFrame({
    'Department': ['IT', 'HR', 'IT', 'Finance', 'HR', 'IT'],
    'Gender': ['F', 'M', 'M', 'M', 'F', 'F'],
    'Salary': [8000, 12000, 15000, 20000, 18000, 22000],
    'Age': [25, 30, 35, 40, 45, 28]
})

# 单列分组
grouped = df.groupby('Department')

# 多列分组
grouped = df.groupby(['Department', 'Gender'])

# 遍历分组
for name, group in grouped:
    print(f"Group: {name}")
    print(group)
    print()

聚合函数

# 单个聚合函数
result = df.groupby('Department')['Salary'].mean()
result = df.groupby('Department')['Salary'].sum()
result = df.groupby('Department')['Salary'].count()

# 多个聚合函数
result = df.groupby('Department')['Salary'].agg(['mean', 'sum', 'count'])

# 不同列不同聚合
result = df.groupby('Department').agg({
    'Salary': ['mean', 'sum'],
    'Age': ['min', 'max']
})

# 自定义聚合函数
def salary_range(x):
    return x.max() - x.min()

result = df.groupby('Department')['Salary'].agg(salary_range)

# 命名聚合
result = df.groupby('Department').agg(
    avg_salary=('Salary', 'mean'),
    total_salary=('Salary', 'sum'),
    emp_count=('Salary', 'count')
)

transform 和 filter

# transform - 保持原 DataFrame 形状
df = pd.DataFrame({
    'Department': ['IT', 'HR', 'IT', 'Finance'],
    'Salary': [8000, 12000, 15000, 20000]
})

# 计算每个部门的平均薪水
df['Dept_Avg_Salary'] = df.groupby('Department')['Salary'].transform('mean')
print(df)
#   Department  Salary  Dept_Avg_Salary
# 0         IT    8000          11500.0
# 1         HR   12000          12000.0
# 2         IT   15000          11500.0
# 3    Finance   20000          20000.0

# 标准化
df['Salary_Standardized'] = (
    df['Salary'] - df.groupby('Department')['Salary'].transform('mean')
) / df.groupby('Department')['Salary'].transform('std')

# filter - 过滤分组
# 只保留员工数大于1的部门
filtered = df.groupby('Department').filter(lambda x: len(x) > 1)

pivot_table 透视表

df = pd.DataFrame({
    'Department': ['IT', 'HR', 'IT', 'Finance', 'HR', 'IT'],
    'Gender': ['F', 'M', 'M', 'M', 'F', 'F'],
    'Salary': [8000, 12000, 15000, 20000, 18000, 22000]
})

# 创建透视表
pivot = pd.pivot_table(
    df,
    values='Salary',
    index='Department',
    columns='Gender',
    aggfunc='mean',
    fill_value=0
)
print(pivot)
# Gender         F       M
# Department
# Finance      0.0  20000.0
# HR       18000.0  12000.0
# IT       15000.0  15000.0

# 多个值和聚合函数
pivot = pd.pivot_table(
    df,
    values=['Salary'],
    index='Department',
    columns='Gender',
    aggfunc=['mean', 'count']
)

crosstab 交叉表

# 交叉表
cross = pd.crosstab(df['Department'], df['Gender'])
print(cross)

# 带汇总
cross = pd.crosstab(df['Department'], df['Gender'], margins=True)

# 归一化
cross = pd.crosstab(df['Department'], df['Gender'], normalize='index')

数据合并与连接

concat 拼接

df1 = pd.DataFrame({
    'A': ['A0', 'A1', 'A2'],
    'B': ['B0', 'B1', 'B2']
})

df2 = pd.DataFrame({
    'A': ['A3', 'A4', 'A5'],
    'B': ['B3', 'B4', 'B5']
})

# 垂直拼接
result = pd.concat([df1, df2])
result = pd.concat([df1, df2], ignore_index=True)  # 重置索引

# 水平拼接
df3 = pd.DataFrame({
    'C': ['C0', 'C1', 'C2'],
    'D': ['D0', 'D1', 'D2']
})
result = pd.concat([df1, df3], axis=1)

# 多个 DataFrame
result = pd.concat([df1, df2, df3], ignore_index=True)

merge 合并

df1 = pd.DataFrame({
    'key': ['K0', 'K1', 'K2', 'K3'],
    'A': ['A0', 'A1', 'A2', 'A3'],
    'B': ['B0', 'B1', 'B2', 'B3']
})

df2 = pd.DataFrame({
    'key': ['K0', 'K1', 'K2', 'K4'],
    'C': ['C0', 'C1', 'C2', 'C3'],
    'D': ['D0', 'D1', 'D2', 'D3']
})

# 内连接(默认)
result = pd.merge(df1, df2, on='key')

# 左连接
result = pd.merge(df1, df2, on='key', how='left')

# 右连接
result = pd.merge(df1, df2, on='key', how='right')

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

# 多键连接
result = pd.merge(df1, df2, on=['key1', 'key2'])

# 不同列名连接
result = pd.merge(df1, df2, left_on='key1', right_on='key2')

# 指示器
result = pd.merge(df1, df2, on='key', how='outer', indicator=True)

join 连接

df1 = pd.DataFrame({
    'A': ['A0', 'A1', 'A2'],
    'B': ['B0', 'B1', 'B2']
}, index=['K0', 'K1', 'K2'])

df2 = pd.DataFrame({
    'C': ['C0', 'C1', 'C2'],
    'D': ['D0', 'D1', 'D2']
}, index=['K0', 'K1', 'K2'])

# 基于索引连接
result = df1.join(df2)
result = df1.join(df2, how='left')
result = df1.join(df2, how='right')
result = df1.join(df2, how='outer')

合并方式对比

方式说明SQL 等价
inner只保留匹配的INNER JOIN
left保留左表全部LEFT JOIN
right保留右表全部RIGHT JOIN
outer保留所有FULL OUTER JOIN

时间序列处理

创建时间序列

import pandas as pd
import numpy as np

# 创建日期范围
dates = pd.date_range('2024-01-01', periods=10, freq='D')
print(dates)

# 不同频率
dates_daily = pd.date_range('2024-01-01', '2024-01-31', freq='D')
dates_monthly = pd.date_range('2024-01-01', periods=12, freq='M')
dates_hourly = pd.date_range('2024-01-01', periods=24, freq='H')

# 创建时间序列 DataFrame
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'value': np.random.randn(100)
})
df = df.set_index('date')

日期时间转换

# 字符串转日期
df = pd.DataFrame({'date': ['2024-01-01', '2024-02-01', '2024-03-01']})
df['date'] = pd.to_datetime(df['date'])

# 指定格式
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')

# 提取日期组件
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.weekday
df['dayofweek'] = df['date'].dt.day_name()
df['quarter'] = df['date'].dt.quarter

# 日期运算
df['next_day'] = df['date'] + pd.Timedelta(days=1)
df['last_month'] = df['date'] - pd.DateOffset(months=1)

时间序列操作

# 设置时间为索引
df = df.set_index('date')

# 时间切片
df['2024-01']              # 2024年1月
df['2024-01-01':'2024-01-31']
df['2024']                 # 2024年全年

# 重采样
daily_data = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=365, freq='D'),
    'value': np.random.randn(365)
}).set_index('date')

monthly_avg = daily_data.resample('M').mean()
weekly_sum = daily_data.resample('W').sum()

# 滚动窗口
rolling_mean = daily_data.rolling(window=7).mean()
rolling_std = daily_data.rolling(window=30).std()

# 移动
shifted = daily_data.shift(1)  # 向下移动1行
diff = daily_data.diff()       # 差分

常用频率别名

别名说明
D
W
M月末
MS月初
Q季末
Y年末
H小时
T/min分钟
S

数据透视表

pivot_table 详解

import pandas as pd
import numpy as np

# 创建示例数据
np.random.seed(42)
df = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'Product': np.random.choice(['A', 'B', 'C'], 100),
    'Region': np.random.choice(['North', 'South', 'East', 'West'], 100),
    'Sales': np.random.randint(100, 1000, 100),
    'Quantity': np.random.randint(1, 50, 100)
})

# 基本透视表
pivot = pd.pivot_table(
    df,
    values='Sales',
    index='Product',
    columns='Region',
    aggfunc='sum',
    fill_value=0
)
print(pivot)

# 多个值
pivot = pd.pivot_table(
    df,
    values=['Sales', 'Quantity'],
    index='Product',
    columns='Region',
    aggfunc='sum'
)

# 多个聚合函数
pivot = pd.pivot_table(
    df,
    values='Sales',
    index='Product',
    columns='Region',
    aggfunc=['sum', 'mean', 'count']
)

# 添加总计
pivot = pd.pivot_table(
    df,
    values='Sales',
    index='Product',
    columns='Region',
    aggfunc='sum',
    margins=True,
    margins_name='Total'
)

melt 逆透视

# 宽表转长表
df_wide = pd.DataFrame({
    'Product': ['A', 'B', 'C'],
    '2023': [100, 200, 300],
    '2024': [150, 250, 350]
})

df_long = pd.melt(
    df_wide,
    id_vars=['Product'],
    value_vars=['2023', '2024'],
    var_name='Year',
    value_name='Sales'
)
print(df_long)
#   Product  Year  Sales
# 0       A  2023    100
# 1       B  2023    200
# 2       C  2023    300
# 3       A  2024    150
# 4       B  2024    250
# 5       C  2024    350

可视化

Pandas 集成了 Matplotlib,可以直接绘图。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows
# plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # macOS
plt.rcParams['axes.unicode_minus'] = False

# 创建示例数据
df = pd.DataFrame({
    'Month': pd.date_range('2024-01-01', periods=12, freq='M'),
    'Sales': np.random.randint(1000, 5000, 12),
    'Profit': np.random.randint(200, 1000, 12)
})
df = df.set_index('Month')

基本图表

# 折线图
df['Sales'].plot(kind='line', title='Monthly Sales')
plt.show()

# 柱状图
df.plot(kind='bar', y=['Sales', 'Profit'])
plt.show()

# 水平柱状图
df.plot(kind='barh', y='Sales')
plt.show()

# 面积图
df.plot(kind='area', stacked=False)
plt.show()

# 散点图
df.plot(kind='scatter', x='Sales', y='Profit')
plt.show()

# 饼图
df['Sales'].plot(kind='pie', autopct='%1.1f%%')
plt.show()

# 直方图
df['Sales'].plot(kind='hist', bins=10)
plt.show()

# 箱线图
df.plot(kind='box')
plt.show()

高级可视化

# 双轴图
fig, ax1 = plt.subplots()
ax1.plot(df.index, df['Sales'], 'b-', label='Sales')
ax1.set_ylabel('Sales', color='b')
ax2 = ax1.twinx()
ax2.plot(df.index, df['Profit'], 'r-', label='Profit')
ax2.set_ylabel('Profit', color='r')
plt.title('Sales and Profit')
plt.show()

# 子图
df.plot(subplots=True, layout=(2, 1), figsize=(10, 8))
plt.show()

# 分组柱状图
df_grouped = df.groupby(df.index.month)['Sales'].sum()
df_grouped.plot(kind='bar')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.title('Monthly Sales Summary')
plt.show()

性能优化

数据类型优化

# 查看内存使用
print(df.memory_usage(deep=True))

# 优化整数类型
df['small_int'] = df['small_int'].astype('int8')    # -128 to 127
df['medium_int'] = df['medium_int'].astype('int16') # -32768 to 32767

# 优化浮点数
df['float_col'] = df['float_col'].astype('float32')

# 使用分类类型
df['category_col'] = df['category_col'].astype('category')

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

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

向量化操作

# ❌ 避免:使用循环
def slow_way(df):
    result = []
    for idx, row in df.iterrows():
        result.append(row['A'] + row['B'])
    return result

# ✅ 推荐:向量化
def fast_way(df):
    return df['A'] + df['B']

# ✅ 推荐:使用 apply
def medium_way(df):
    return df.apply(lambda row: row['A'] + row['B'], axis=1)

分块处理大数据

# 分块读取大型 CSV
chunk_size = 10000
chunks = pd.read_csv('large_file.csv', chunksize=chunk_size)

results = []
for chunk in chunks:
    # 处理每个块
    processed = chunk[chunk['value'] > 100]
    results.append(processed)

# 合并结果
result = pd.concat(results, ignore_index=True)

使用 eval 和 query

# ❌ 普通方式
result = df[(df['A'] > 10) & (df['B'] < 20)]

# ✅ 使用 query(更清晰,对大数据更快)
result = df.query('A > 10 and B < 20')

# ✅ 使用 eval 进行复杂计算
df.eval('C = A + B', inplace=True)
df.eval('D = A * B / C', inplace=True)

实战案例

案例 1:销售数据分析

import pandas as pd
import numpy as np

# 创建模拟销售数据
np.random.seed(42)
n_records = 1000

sales_data = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=n_records, freq='D'),
    'Product': np.random.choice(['Product_A', 'Product_B', 'Product_C'], n_records),
    'Region': np.random.choice(['North', 'South', 'East', 'West'], n_records),
    'Salesperson': np.random.choice(['Alice', 'Bob', 'Charlie', 'David'], n_records),
    'Quantity': np.random.randint(1, 100, n_records),
    'Unit_Price': np.random.uniform(10, 100, n_records).round(2)
})

# 计算销售额
sales_data['Revenue'] = sales_data['Quantity'] * sales_data['Unit_Price']

# 基本统计
print("总销售额:", sales_data['Revenue'].sum())
print("平均订单金额:", sales_data['Revenue'].mean())
print("总订单数:", len(sales_data))

# 按产品分析
product_stats = sales_data.groupby('Product').agg({
    'Revenue': ['sum', 'mean', 'count'],
    'Quantity': 'sum'
}).round(2)
print("\n产品销售统计:")
print(product_stats)

# 按地区分析
region_stats = sales_data.groupby('Region')['Revenue'].sum().sort_values(ascending=False)
print("\n地区销售排名:")
print(region_stats)

# 按销售人员分析
salesperson_stats = sales_data.groupby('Salesperson').agg({
    'Revenue': 'sum',
    'Quantity': 'sum'
}).sort_values('Revenue', ascending=False)
print("\n销售人员业绩:")
print(salesperson_stats)

# 月度趋势
sales_data['Month'] = sales_data['Date'].dt.to_period('M')
monthly_sales = sales_data.groupby('Month')['Revenue'].sum()
print("\n月度销售趋势:")
print(monthly_sales)

# Top 10 订单
top_orders = sales_data.nlargest(10, 'Revenue')[['Date', 'Product', 'Salesperson', 'Revenue']]
print("\nTop 10 订单:")
print(top_orders)

案例 2:学生成绩分析

import pandas as pd
import numpy as np

# 创建学生成绩数据
np.random.seed(42)
students = pd.DataFrame({
    'Student_ID': range(1, 101),
    'Name': [f'Student_{i}' for i in range(1, 101)],
    'Class': np.random.choice(['Class_A', 'Class_B', 'Class_C'], 100),
    'Math': np.random.randint(50, 100, 100),
    'English': np.random.randint(50, 100, 100),
    'Science': np.random.randint(50, 100, 100)
})

# 计算总分和平均分
students['Total'] = students[['Math', 'English', 'Science']].sum(axis=1)
students['Average'] = students[['Math', 'English', 'Science']].mean(axis=1)

# 等级划分
def get_grade(score):
    if score >= 90:
        return 'A'
    elif score >= 80:
        return 'B'
    elif score >= 70:
        return 'C'
    elif score >= 60:
        return 'D'
    else:
        return 'F'

students['Grade'] = students['Average'].apply(get_grade)

# 班级统计
class_stats = students.groupby('Class').agg({
    'Math': ['mean', 'max', 'min'],
    'English': ['mean', 'max', 'min'],
    'Science': ['mean', 'max', 'min'],
    'Average': 'mean'
}).round(2)
print("班级统计:")
print(class_stats)

# 科目统计
subject_stats = students[['Math', 'English', 'Science']].describe()
print("\n科目统计:")
print(subject_stats)

# 等级分布
grade_dist = students['Grade'].value_counts()
print("\n等级分布:")
print(grade_dist)

# 优秀学生(平均分 >= 85)
excellent_students = students[students['Average'] >= 85].sort_values('Average', ascending=False)
print(f"\n优秀学生人数: {len(excellent_students)}")
print(excellent_students.head(10))

# 不及格学生
failed_students = students[students['Average'] < 60]
print(f"\n不及格学生人数: {len(failed_students)}")
if len(failed_students) > 0:
    print(failed_students)

# 相关性分析
correlation = students[['Math', 'English', 'Science']].corr()
print("\n科目相关性:")
print(correlation)

案例 3:股票数据分析

import pandas as pd
import numpy as np

# 创建模拟股票数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=252, freq='B')  # 交易日

stock_data = pd.DataFrame({
    'Date': dates,
    'Open': 100 + np.cumsum(np.random.randn(252) * 2),
    'High': 0,
    'Low': 0,
    'Close': 0,
    'Volume': np.random.randint(1000000, 5000000, 252)
})

# 计算 High, Low, Close
stock_data['Close'] = stock_data['Open'] + np.random.randn(252) * 2
stock_data['High'] = stock_data[['Open', 'Close']].max(axis=1) + abs(np.random.randn(252))
stock_data['Low'] = stock_data[['Open', 'Close']].min(axis=1) - abs(np.random.randn(252))

# 设置日期为索引
stock_data = stock_data.set_index('Date')

# 计算日收益率
stock_data['Return'] = stock_data['Close'].pct_change()

# 计算移动平均线
stock_data['MA_5'] = stock_data['Close'].rolling(window=5).mean()
stock_data['MA_20'] = stock_data['Close'].rolling(window=20).mean()
stock_data['MA_60'] = stock_data['Close'].rolling(window=60).mean()

# 基本统计
print("股票基本统计:")
print(f"起始价格: {stock_data['Close'].iloc[0]:.2f}")
print(f"结束价格: {stock_data['Close'].iloc[-1]:.2f}")
print(f"最高价格: {stock_data['High'].max():.2f}")
print(f"最低价格: {stock_data['Low'].min():.2f}")
print(f"平均成交量: {stock_data['Volume'].mean():.0f}")

# 收益率统计
print("\n收益率统计:")
print(f"平均日收益率: {stock_data['Return'].mean():.4f}")
print(f"收益率标准差: {stock_data['Return'].std():.4f}")
print(f"年化收益率: {stock_data['Return'].mean() * 252:.4f}")
print(f"年化波动率: {stock_data['Return'].std() * np.sqrt(252):.4f}")

# 月度表现
stock_data['Month'] = stock_data.index.to_period('M')
monthly_returns = stock_data.groupby('Month')['Return'].sum()
print("\n月度收益率:")
print(monthly_returns)

# 最佳和最差交易日
best_day = stock_data['Return'].nlargest(1)
worst_day = stock_data['Return'].nsmallest(1)
print(f"\n最佳交易日: {best_day.index[0].date()} ({best_day.values[0]:.2%})")
print(f"最差交易日: {worst_day.index[0].date()} ({worst_day.values[0]:.2%})")

案例 4:数据清洗实战

import pandas as pd
import numpy as np

# 创建脏数据
dirty_data = pd.DataFrame({
    'Name': ['Alice', 'bob', 'CHARLIE', 'david', ' Eve ', None, 'Grace'],
    'Age': [25, '30', 35, None, 40, 45, 'invalid'],
    'Email': ['alice@example.com', 'BOB@TEST.COM', 'charlie@example.com',
              None, 'eve@test.com', 'frank@example.com', 'grace@test.com'],
    'Salary': ['8000', '12000', None, '20000', '18000', '22000', '15000'],
    'Join_Date': ['2020-01-15', '2019-06-20', 'invalid', '2021-03-10',
                  '2018-11-25', '2022-07-01', '2020-09-15']
})

print("原始数据:")
print(dirty_data)
print("\n数据类型:")
print(dirty_data.dtypes)

# 1. 处理姓名
dirty_data['Name'] = dirty_data['Name'].str.strip().str.title()
print("\n处理姓名后:")
print(dirty_data['Name'])

# 2. 处理年龄
dirty_data['Age'] = pd.to_numeric(dirty_data['Age'], errors='coerce')
print("\n处理年龄后:")
print(dirty_data['Age'])

# 3. 处理邮箱
dirty_data['Email'] = dirty_data['Email'].str.lower()
print("\n处理邮箱后:")
print(dirty_data['Email'])

# 4. 处理薪水
dirty_data['Salary'] = pd.to_numeric(dirty_data['Salary'], errors='coerce')
print("\n处理薪水后:")
print(dirty_data['Salary'])

# 5. 处理日期
dirty_data['Join_Date'] = pd.to_datetime(dirty_data['Join_Date'], errors='coerce')
print("\n处理日期后:")
print(dirty_data['Join_Date'])

# 6. 处理缺失值
print("\n缺失值统计:")
print(dirty_data.isnull().sum())

# 填充数值列的中位数
dirty_data['Age'] = dirty_data['Age'].fillna(dirty_data['Age'].median())
dirty_data['Salary'] = dirty_data['Salary'].fillna(dirty_data['Salary'].median())

# 删除邮箱为空的行
dirty_data = dirty_data.dropna(subset=['Email'])

# 删除姓名为空的行
dirty_data = dirty_data.dropna(subset=['Name'])

print("\n清洗后的数据:")
print(dirty_data)
print("\n最终数据类型:")
print(dirty_data.dtypes)
print("\n缺失值检查:")
print(dirty_data.isnull().sum())

总结

核心知识点回顾

  1. 数据结构:Series 和 DataFrame
  2. 数据读写:CSV、Excel、JSON、SQL 等格式
  3. 数据查看:head、tail、info、describe
  4. 数据选择:loc、iloc、布尔索引、query
  5. 数据清洗:缺失值、重复值、类型转换
  6. 数据转换:apply、map、replace、离散化
  7. 数据筛选:条件筛选、排序、采样
  8. 统计分析:基本统计、分组聚合
  9. 数据合并:concat、merge、join
  10. 时间序列:日期处理、重采样、滚动窗口
  11. 透视表:pivot_table、crosstab、melt
  12. 可视化:内置绘图功能
  13. 性能优化:数据类型、向量化、分块处理

学习建议

  • 多实践:通过真实数据集练习
  • 理解索引:loc 和 iloc 的区别至关重要
  • 掌握 groupby:分组聚合是数据分析的核心
  • 善用文档:Pandas 官方文档非常详细
  • 结合 NumPy:两者配合使用效果更佳
  • 关注性能:大数据集时注意优化

下一步

掌握 Pandas 后,建议继续学习:

  • Matplotlib/Seaborn:高级数据可视化
  • Scikit-learn:机器学习
  • SQL:数据库查询
  • Spark:大规模数据处理

参考资料