做数据科学项目,最常打交道的就是文件读写、表格操作和数值计算。看似基础,但细节一错,后续分析全跑偏。这篇文章围绕 CSV/JSON 文件处理、pandas DataFrame 操作和 NumPy 数组计算三个核心板块,把容易踩坑的地方和实用模式梳理清楚,最后附一段可直接运行的整合示例。
CSV 文件:读写的隐坑比你想的多
CSV 是数据交换最普遍的格式,但它的"简单"只是表象。
读取时的常见问题:
- 编码不是 UTF-8(中文数据尤其常见),直接
open会报UnicodeDecodeError。 - 分隔符不一定是逗号——有些文件用 tab 或分号。
- 首行可能不是列名,或者列名里有空格。
用 pandas 读 CSV 时,关键参数要显式指定:
import pandas as pd
df = pd.read_csv(
"sales_data.csv",
encoding="gbk", # 中文 Windows 导出的文件常见 gbk
sep=",", # 非逗号分隔时改这里
header=0, # 首行是列名;无列名时设 None
skiprows=[1, 2], # 跳过前两行垃圾数据
na_values=["NA", "null"] # 把这些字符串自动转为 NaN
)
写入时要注意:
pandas 默认写 CSV 会带行索引,大多数下游系统不需要这列:
df.to_csv("output.csv", index=False, encoding="utf-8-sig")
utf-8-sig 加了 BOM 头,Excel 打开不会乱码——这是个值得记住的小技巧。
JSON 文件:嵌套结构的拆解才是难点
JSON 比 CSV 灵活,但也更"乱"。真实数据经常是嵌套的,直接 json.load 之后拿到一个深层字典,怎么拍平成表格?
基础读写:
import json
# 读取
with open("config.json", "r", encoding="utf-8") as f:
data = json.load(f)
# 写入
with open("output.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
ensure_ascii=False 保证中文不变成 \uXXXX 转义;indent=2 让输出可读。
嵌套 JSON → DataFrame:
假设数据是这样的结构:
[
{"name": "Alice", "scores": {"math": 90, "english": 85}},
{"name": "Bob", "scores": {"math": 78, "english": 92}}
]
直接 pd.DataFrame(data) 只会得到 scores 列里塞着字典。正确做法是用 json_normalize:
from pandas import json_normalize
flat_df = json_normalize(data)
# 结果:name | scores.math | scores.english 三列,干净整齐
print(flat_df)
这比手写循环拆字典快得多,也更不容易出错。
pandas DataFrame:五个高频操作模式
DataFrame 是数据科学的主力工具。以下五个操作覆盖日常 80% 的场景。
1. 筛选与条件查询
# 单条件
high_math = df[df["math"] > 80]
# 多条件组合
mask = (df["math"] > 80) & (df["english"] < 90)
result = df.loc[mask, ["name", "math", "english"]]
注意:多条件必须用 & / |,不能用 Python 的 and / or;每个条件要用括号包起来,否则运算优先级会出错。
2. 分组聚合
summary = df.groupby("department").agg(
avg_salary=("salary", "mean"),
head_count=("salary", "count"),
max_salary=("salary", "max")
)
用命名聚合(agg 里传元组)比之后重命名列名更清晰。
3. 缺失值处理
# 查看每列缺失数量
missing = df.isna().sum()
# 填充:数值列用中位数,分类列用 "未知"
df["age"] = df["age"].fillna(df["age"].median())
df["city"] = df["city"].fillna("未知")
别盲目 fillna(0)——零值和中位数含义完全不同,选哪个要看业务语境。
4. 合并两张表
merged = pd.merge(
orders, customers,
on="customer_id", # 连接键
how="left" # 左连接:保留所有订单
)
how 参数选 left、right、inner 还是 outer,取决于你想保留哪边的数据。先想清楚业务需求再选。
5. 列类型转换
df["date"] = pd.to_datetime(df["date_str"], format="%Y-%m-%d")
df["amount"] = df["amount"].astype(float)
日期和金额列不转换类型,后续排序、计算、绘图全会出问题。
NumPy 数组:向量化思维替代循环
NumPy 的核心优势是向量化运算——用数组表达式替代逐元素循环,速度差几十倍。
创建与基本运算:
import numpy as np
# 从列表创建
arr = np.array([1, 2, 3, 4, 5])
# 批量运算——不需要写循环
squared = arr ** 2 # [1, 4, 9, 16, 25]
scaled = arr * 2.5 + 10 # [12.5, 15.0, 17.5, 20.0, 22.5]
# 统计
print(arr.mean(), arr.std(), arr.max(), arr.min())
二维数组与矩阵操作:
matrix = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# 按行求和
row_sums = matrix.sum(axis=1) # [6, 15, 24]
# 按列均值
col_means = matrix.mean(axis=0) # [4., 5., 6.]
# 矩阵乘法
result = matrix @ matrix.T # 等价于 np.dot(matrix, matrix.T)
广播机制——形状不同也能运算:
row_vector = np.array([[10, 20, 30]]) # shape (1, 3)
col_vector = np.array([[1], [2], [3]]) # shape (3, 1)
# 广播:自动扩展成 (3, 3) 再相加
broadcast_result = row_vector + col_vector
# [[11, 21, 31],
# [12, 22, 32],
# [13, 23, 33]]
理解广播能让你少写大量 reshape 和循环代码。
布尔索引——比循环筛选快得多:
data = np.random.randn(10000) # 一万个随机数
positives = data[data > 0] # 一步筛出所有正数
count = (data > 0).sum() # 正数个数,sum 对 True/False 计数
整合实战:从原始文件到分析结果
下面这段代码模拟了一个完整的小流程:读 CSV → 清洗 → NumPy 计算 → 输出 JSON。可以直接复制运行。
import pandas as pd
import numpy as np
import json
from pathlib import Path
# ---- 1. 生成示例 CSV(如果文件不存在)----
sample_csv = Path("sample_scores.csv")
if not sample_csv.exists():
pd.DataFrame({
"student": ["Alice", "Bob", "Carol", "Dave", "Eve"],
"math": [92, 78, 85, None, 90],
"english": [88, 95, 79, 82, None],
"department": ["A", "B", "A", "B", "A"]
}).to_csv(sample_csv, index=False)
# ---- 2. 读取并清洗 ----
df = pd.read_csv(sample_csv)
df["math"] = df["math"].fillna(df["math"].median())
df["english"] = df["english"].fillna(df["english"].median())
# ---- 3. pandas 分组统计 ----
group_stats = df.groupby("department").agg(
avg_math=("math", "mean"),
avg_english=("english", "mean"),
count=("student", "count")
).round(2)
# ---- 4. NumPy 向量化计算 ----
math_arr = df["math"].to_numpy()
eng_arr = df["english"].to_numpy()
total_arr = math_arr + eng_arr # 向量化求和
df["total"] = total_arr
df["rank"] = df["total"].rank(ascending=False).astype(int)
# ---- 5. 输出 JSON ----
output = {
"group_summary": group_stats.to_dict(),
"top_student": df.loc[df["rank"] == 1, ["student", "total"]].to_dict("records")[0],
"math_stats": {
"mean": float(np.mean(math_arr)),
"std": float(np.std(math_arr)),
"max": float(np.max(math_arr))
}
}
with open("analysis_result.json", "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print("=== 分组统计 ===")
print(group_stats)
print("\n=== 排名表 ===")
print(df[["student", "math", "english", "total", "rank"]])
print("\n=== JSON 已写入 analysis_result.json ===")
运行后你会看到分组均值、排名表,以及一个结构清晰的 JSON 文件。把这段代码里的文件路径和列名换成你自己的数据,就能直接用。
检查清单:上手前的几个确认项
每次拿到新数据集,先过一遍这张清单,能省掉后续大量排查时间:
| 检查项 | 怎么做 | 常见坑 |
|---|---|---|
| 编码 | pd.read_csv(..., encoding=?) |
中文文件默认 UTF-8 会炸 |
| 分隔符 | df.head() 看列是否挤成一列 |
tab/分号分隔的文件被当逗号解析 |
| 缺失值 | df.isna().sum() |
空格字符串不算 NaN,需手动指定 |
| 列类型 | df.dtypes |
日期是字符串、金额是 object 类型 |
| 嵌套 JSON | json_normalize |
直接 DataFrame 会得到字典列 |
| 数组形状 | arr.shape |
广播出错多半是形状没对齐 |
这些基本功不花哨,但决定了后续建模、可视化的地基是否稳固。花二十分钟把这些模式跑通,比事后花两小时排查一个编码错误划算得多。