8 minutes
Pandas 基础
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,让数据"说话"——用可视化来呈现你发现的洞察。