Pandas 是 Python 数据分析的核心库。它提供了两种主要的数据结构——Series(一维)和 DataFrame(二维),让数据操作变得直观而高效。如果说 NumPy 是"Python 中的数组",那 Pandas 就是"Python 中的 Excel"。

Series:一维数据结构

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

import pandas as pd
import numpy as np

# 从列表创建 Series
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([1, 3, 5, 7], index=["a", "b", "c", "d"])
print(s)
# a    1
# b    3
# c    5
# d    7
# dtype: int64

# 从字典创建 Series
population = pd.Series({
    "北京": 2154,
    "上海": 2487,
    "广州": 1868,
    "深圳": 1756
}, name="人口(万)")

print(population)
# 北京    2154
# 上海    2487
# 广州    1868
# 深圳    1756
# Name: 人口(万), dtype: int64

# Series 的属性
print(population.values)   # [2154 2487 1868 1756]
print(population.index)    # Index(['北京', '上海', '广州', '深圳'], dtype='object')
print(population.name)     # 人口(万)

# Series 的索引和切片
print(population["北京"])             # 2154
print(population[["北京", "广州"]])    # 多标签索引
print(population[population > 2000])  # 布尔索引

DataFrame:二维数据结构

DataFrame 是 Pandas 中最重要的数据结构,可以理解为带行标签和列标签的表格:

# 从字典创建 DataFrame
data = {
    "姓名": ["张三", "李四", "王五", "赵六"],
    "年龄": [25, 30, 28, 35],
    "城市": ["北京", "上海", "广州", "深圳"],
    "薪资": [15000, 20000, 18000, 25000]
}

df = pd.DataFrame(data)
print(df)
#    姓名  年龄  城市     薪资
# 0  张三  25  北京  15000
# 1  李四  30  上海  20000
# 2  王五  28  广州  18000
# 3  赵六  35  深圳  25000

# 从 NumPy 数组创建
arr = np.random.randn(4, 3)
df = pd.DataFrame(arr, columns=["A", "B", "C"],
                  index=["行1", "行2", "行3", "行4"])
print(df)

# 查看 DataFrame 信息
print(df.shape)          # (4, 3)
print(df.columns)        # Index(['A', 'B', 'C'], dtype='object')
print(df.index)          # Index(['行1', '行2', '行3', '行4'], dtype='object')
print(df.dtypes)         # 各列数据类型
print(df.values)         # 底层 NumPy 数组

读取外部数据

Pandas 支持读取多种常见数据格式,这是数据分析的第一步:

# CSV 文件(最常用)
df = pd.read_csv("data/sales.csv")
df = pd.read_csv("data/sales.csv", encoding="utf-8")
df = pd.read_csv("data/sales.csv", encoding="gbk")  # 中文 Windows 常见编码

# 常用参数
df = pd.read_csv(
    "data/sales.csv",
    sep=",",                # 分隔符
    header=0,               # 表头在第 0 行
    index_col="日期",       # 设置索引列
    usecols=["日期", "销售额", "成本"],  # 只读取指定列
    dtype={"销售额": float},  # 指定数据类型
    parse_dates=["日期"],   # 解析日期列
    na_values=["", "NA", "null"],  # 将指定值视为缺失值
    nrows=1000,             # 只读取前 1000 行
    skiprows=[0, 2, 3],     # 跳过指定行
)

# Excel 文件
df = pd.read_excel("data/report.xlsx", sheet_name="Sheet1")

# SQL 数据库
from sqlalchemy import create_engine
engine = create_engine("sqlite:///data.db")
df = pd.read_sql("SELECT * FROM sales WHERE date >= '2024-01-01'", engine)

# JSON 文件
df = pd.read_json("data/data.json")

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

探索性数据查看

拿到数据后的第一步是了解数据的基本情况:

# 假设我们有一个销售数据集
df = pd.read_csv("data/sales.csv")

# 查看前几行
print(df.head())           # 默认前 5 行
print(df.head(10))         # 前 10 行

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

# 随机抽样查看
print(df.sample(5))        # 随机 5 行

# 基本信息
print(df.info())
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 10000 entries, 0 to 9999
# Data columns (total 8 columns):
#  #   Column      Non-Null Count  Dtype
# ---  ------      --------------  -----
#  0   日期         10000 non-null  object
#  1   产品         10000 non-null  object
#  2   类别         10000 non-null  object
#  3   销量         10000 non-null  int64
#  4   单价         10000 non-null  float64
#  5   金额         10000 non-null  float64
#  6   区域         10000 non-null  object
#  7   销售员       9500 non-null   object
# dtypes: float64(2), int64(1), object(5)

# 描述性统计
print(df.describe())
#              销量          单价          金额
# count  10000.000  10000.000  10000.000
# mean      52.340     45.678   2345.670
# std       28.123     30.456   1567.890
# min        1.000      5.000     12.000
# 25%       28.000     20.000    890.000
# 50%       50.000     40.000   2100.000
# 75%       75.000     65.000   3450.000
# max      100.000    200.000  12000.000

# 对非数值列也进行统计
print(df.describe(include="object"))

# 各列数据类型
print(df.dtypes)

# 缺失值统计
print(df.isna().sum())

列选择和筛选

# 选择单列(返回 Series)
product_col = df["产品"]
print(type(product_col))  # <class 'pandas.core.series.Series'>

# 选择多列(返回 DataFrame)
subset = df[["产品", "销量", "金额"]]

# 按行号选择
first_100 = df[:100]  # 前 100 行

# 使用 loc(按标签选择)
print(df.loc[0])                    # 索引为 0 的行
print(df.loc[0:5, ["产品", "金额"]])  # 指定行范围和列
print(df.loc[df["销量"] > 80])       # 筛选销量 > 80 的行

# 使用 iloc(按位置选择)
print(df.iloc[0])                    # 第一行
print(df.iloc[0:5, 1:4])             # 前 5 行,第 2 到 4 列
print(df.iloc[[0, 2, 4], [1, 3]])    # 指定行和列

# 条件筛选
high_sales = df[df["销量"] > 80]
beijing_sales = df[df["区域"] == "北京"]
profitable = df[(df["金额"] > 5000) & (df["销量"] > 50)]

# isin 筛选
key_regions = ["北京", "上海", "广州"]
region_filtered = df[df["区域"].isin(key_regions)]

# 字符串方法
name_filtered = df[df["产品"].str.contains("手机")]
name_start = df[df["产品"].str.startswith("华")]

添加和删除列

# 添加新列(基于已有列计算)
df["利润率"] = df["金额"] / (df["销量"] * df["单价"]) * 100
df["折扣"] = df["单价"] * 0.9
df["是否高额"] = df["金额"] > 5000  # 布尔列

# 使用 assign 方法(返回新 DataFrame,不影响原数据)
df_with_ratio = df.assign(
    利润率=lambda x: x["金额"] / (x["销量"] * x["单价"]) * 100,
    折扣=lambda x: x["单价"] * 0.9
)

# 条件赋值(使用 np.where)
import numpy as np
df["评级"] = np.where(df["金额"] > 5000, "高", "低")

# 使用 loc 进行条件赋值
df.loc[df["金额"] > 10000, "评级"] = "超高"
df.loc[(df["金额"] > 5000) & (df["金额"] <= 10000), "评级"] = "高"
df.loc[df["金额"] <= 5000, "评级"] = "一般"

# 删除列
df_dropped = df.drop(columns=["利润率", "折扣"])  # 返回新 DataFrame
df.drop(columns=["利润率", "折扣"], inplace=True)  # 原地删除

# 删除行
df_clean = df.drop(index=[0, 1, 2])  # 删除前三行
df_clean = df[df["金额"] > 0]         # 只保留金额 > 0 的行

处理缺失数据

现实世界的数据很少是完整的,处理缺失值是数据分析的必备技能:

# 检测缺失值
print(df.isna())               # 每个元素是否缺失
print(df.isna().sum())         # 每列缺失数量
print(df.isna().sum().sum())   # 总缺失数量

# 删除缺失值
df_clean = df.dropna()                # 删除包含任何缺失值的行
df_clean = df.dropna(thresh=5)        # 至少要有 5 个非缺失值才保留
df_clean = df.dropna(subset=["金额"])  # 只在指定列检查缺失值
df_clean = df.dropna(axis=1)          # 删除包含缺失值的列

# 填充缺失值
df_filled = df.fillna(0)                       # 用 0 填充
df_filled = df.fillna(df.mean())               # 用该列均值填充
df_filled = df.fillna(method="ffill")          # 前向填充
df_filled = df.fillna(method="bfill")          # 后向填充
df_filled = df.fillna({"销售员": "未知", "金额": 0})  # 不同列用不同值

# 插值填充
df_filled = df.interpolate()  # 线性插值

# 常见实战:处理缺失值的流程
def clean_missing_data(df):
    """处理缺失数据的标准流程"""
    print(f"原始数据形状: {df.shape}")
    print(f"缺失值统计:\n{df.isna().sum()}")
    
    # 删除缺失比例超过 50% 的列
    missing_ratio = df.isna().sum() / len(df)
    cols_to_drop = missing_ratio[missing_ratio > 0.5].index
    df = df.drop(columns=cols_to_drop)
    print(f"删除高缺失列后形状: {df.shape}")
    
    # 数值列用均值填充
    num_cols = df.select_dtypes(include=[np.number]).columns
    df[num_cols] = df[num_cols].fillna(df[num_cols].mean())
    
    # 类别列用众数填充
    cat_cols = df.select_dtypes(include=["object"]).columns
    for col in cat_cols:
        df[col] = df[col].fillna(df[col].mode()[0] if not df[col].mode().empty else "未知")
    
    print(f"清理后缺失值: {df.isna().sum().sum()}")
    return df

分组和聚合

分组-应用-组合(Split-Apply-Combine)是数据分析的核心模式:

# 单列分组
region_stats = df.groupby("区域")["金额"].agg(["sum", "mean", "count", "std"])
print(region_stats)

# 多列分组
product_region = df.groupby(["区域", "产品"])["金额"].sum().unstack()

# 多种聚合方式
summary = df.groupby("区域").agg({
    "金额": ["sum", "mean", "count"],
    "销量": ["sum", "mean"],
    "单价": "mean"
})

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

summary = df.groupby("区域")["金额"].agg(["sum", "mean", range_func])

# transform:保持原行数的分组操作
df["区域平均金额"] = df.groupby("区域")["金额"].transform("mean")
df["金额与区域差异"] = df["金额"] - df["区域平均金额"]

# 过滤分组
# 只保留总金额 > 100000 的区域
filtered = df.groupby("区域").filter(lambda x: x["金额"].sum() > 100000)

# 实战:多维度销售分析
def sales_analysis(df):
    """多维度销售分析"""
    print("=" * 60)
    print("销售多维分析报告")
    print("=" * 60)
    
    # 1. 按区域汇总
    print("\n【按区域汇总】")
    region_summary = df.groupby("区域").agg(
        总销售额=("金额", "sum"),
        平均销售额=("金额", "mean"),
        总销量=("销量", "sum"),
        订单数=("金额", "count")
    ).round(2)
    print(region_summary)
    
    # 2. 产品表现
    print("\n【产品表现 Top 10】")
    product_summary = df.groupby("产品").agg(
        总销售额=("金额", "sum"),
        平均单价=("单价", "mean"),
        售出数量=("销量", "sum")
    ).sort_values("总销售额", ascending=False).head(10)
    print(product_summary)
    
    # 3. 区域-产品交叉分析
    print("\n【区域 x 产品交叉分析】")
    cross = pd.pivot_table(
        df,
        values="金额",
        index="区域",
        columns="产品",
        aggfunc="sum",
        fill_value=0
    )
    print(cross)
    
    return region_summary

数据合并

# concat:纵向或横向拼接
df1 = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
df2 = pd.DataFrame({"A": [5, 6], "B": [7, 8]})

vertical = pd.concat([df1, df2], axis=0)  # 纵向拼接(增加行)
horizontal = pd.concat([df1, df2], axis=1)  # 横向拼接(增加列)

# merge:类似 SQL 的 JOIN
customers = pd.DataFrame({
    "客户ID": [1, 2, 3, 4],
    "姓名": ["张三", "李四", "王五", "赵六"],
    "城市": ["北京", "上海", "广州", "深圳"]
})

orders = pd.DataFrame({
    "订单号": ["ORD001", "ORD002", "ORD003", "ORD004"],
    "客户ID": [1, 2, 1, 3],
    "金额": [500, 1500, 800, 2000],
    "日期": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"]
})

# 内连接(只保留匹配的记录)
result = pd.merge(customers, orders, on="客户ID", how="inner")
print(result)

# 左连接(保留左边所有记录)
result = pd.merge(customers, orders, on="客户ID", how="left")

# 右连接
result = pd.merge(customers, orders, on="客户ID", how="right")

# 外连接(保留所有记录)
result = pd.merge(customers, orders, on="客户ID", how="outer")

# 按不同键连接
result = pd.merge(customers, orders, left_on="客户ID", right_on="客户ID")

# join:通过索引连接
df_left = pd.DataFrame({"A": [1, 2]}, index=["x", "y"])
df_right = pd.DataFrame({"B": [3, 4]}, index=["x", "y"])
result = df_left.join(df_right)

基本绘图集成

Pandas 内置了对 Matplotlib 的集成,可以快速创建图表:

import matplotlib.pyplot as plt

# 折线图
df.groupby("日期")["金额"].sum().plot(
    kind="line", figsize=(12, 5), title="日销售额趋势"
)
plt.show()

# 柱状图
df.groupby("区域")["金额"].sum().plot(
    kind="bar", figsize=(8, 5), title="各区域销售额"
)
plt.show()

# 直方图
df["金额"].plot(
    kind="hist", bins=30, figsize=(8, 5), title="销售额分布"
)
plt.show()

# 箱线图
df.boxplot(column="金额", by="区域", figsize=(8, 5))
plt.show()

# 散点图
df.plot(
    kind="scatter", x="销量", y="金额", alpha=0.5, figsize=(8, 5)
)
plt.show()

实用技巧

# 1. 链式操作
result = (
    df
    .query("金额 > 1000")              # 筛选
    .groupby("区域")                    # 分组
    ["金额"]                            # 选择列
    .agg(["sum", "mean", "count"])      # 聚合
    .sort_values("sum", ascending=False) # 排序
    .round(2)                           # 保留两位小数
)

# 2. apply 和 applymap
df["金额等级"] = df["金额"].apply(
    lambda x: "高" if x > 5000 else ("中" if x > 2000 else "低")
)

# 3. 数据透视表
pivot = pd.pivot_table(
    df,
    values="金额",
    index="区域",
    columns="产品类别",
    aggfunc="sum",
    fill_value=0,
    margins=True,        # 显示总计
    margins_name="合计"
)

# 4. 交叉表(频次统计)
cross_tab = pd.crosstab(df["区域"], df["产品类别"])

# 5. 时间序列处理
df["日期"] = pd.to_datetime(df["日期"])
df.set_index("日期", inplace=True)
monthly = df.resample("M")["金额"].sum()  # 按月汇总
weekly = df.resample("W")["金额"].sum()   # 按周汇总

实战:完整的分析流程

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

# 生成模拟销售数据
def generate_sales_data(n=1000):
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=n, freq="D")
    products = ["手机A", "手机B", "笔记本X", "笔记本Y", "平板Z"]
    regions = ["北京", "上海", "广州", "深圳", "杭州"]
    
    data = {
        "日期": np.random.choice(dates, n),
        "产品": np.random.choice(products, n),
        "区域": np.random.choice(regions, n),
        "销量": np.random.randint(1, 100, n),
        "单价": np.random.choice([2999, 3999, 4999, 5999, 7999], n),
    }
    df = pd.DataFrame(data)
    df["金额"] = df["销量"] * df["单价"]
    return df

df = generate_sales_data(500)

# 1. 数据概览
print("数据概览:")
print(f"共 {len(df)} 条记录")
print(f"日期范围: {df['日期'].min()}{df['日期'].max()}")
print()

# 2. 月度趋势
df["月份"] = df["日期"].dt.to_period("M")
monthly = df.groupby("月份")["金额"].agg(["sum", "mean"])

# 3. Top 分析
top_products = df.groupby("产品")["金额"].sum().sort_values(ascending=False)
top_regions = df.groupby("区域")["金额"].sum().sort_values(ascending=False)

print("Top 产品(按销售额):")
print(top_products)
print()

print("Top 区域(按销售额):")
print(top_regions)
print()

# 4. 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 月度趋势
monthly["sum"].plot(ax=axes[0, 0], title="月度销售额趋势", marker="o")
axes[0, 0].set_ylabel("销售额")

# 产品对比
top_products.plot(ax=axes[0, 1], kind="bar", title="各产品销售额", color="skyblue")
axes[0, 1].set_ylabel("销售额")

# 区域对比
top_regions.plot(ax=axes[1, 0], kind="pie", title="各区销售额占比", autopct="%1.1f%%")

# 销量分布
df["销量"].plot(ax=axes[1, 1], kind="hist", bins=20, title="销量分布", edgecolor="white")
axes[1, 1].set_xlabel("销量")

plt.tight_layout()
plt.show()

print("分析完成!")

下一步

Pandas 是数据分析的利器,掌握它意味着你可以高效地完成数据清洗、转换、聚合和探索的全流程。下一篇将介绍 Matplotlib 和 Seaborn,让数据"说话"——用可视化来呈现你发现的洞察。