Python 已成为数据分析的事实标准语言,其优势不仅在于 pandas、NumPy、Matplotlib 等库的成熟生态,更在于它能将数据获取、清洗、探索、建模、可视化与工程化部署串联为一条可复现的流水线。专业数据分析的核心不是“会调 API”,而是理解数据质量、统计假设、计算性能与业务语义之间的权衡。本文从工具链、数据加载、清洗、EDA、可视化、统计建模、性能优化、可复现性与反模式等维度,给出一套可落地的 Python 数据分析方法,并附带完整代码示例。
关键词:Python;数据分析;pandas;Polars;DuckDB;EDA;特征工程;可复现性
专业数据分析通常遵循以下流程:
问题定义 → 数据获取 → 数据清洗 → 探索性分析 → 统计建模
→ 可视化 → 结论与建议 → 可复现交付关键原则:
推荐工具链:
pip install pandas numpy matplotlib seaborn scipy statsmodels scikit-learn polars duckdbimport pandas as pd
import numpy as np
# 生成模拟数据
rng = np.random.default_rng(42)
n = 1000
df = pd.DataFrame({
"user_id": np.arange(1, n + 1),
"city": rng.choice(["北京", "上海", "广州", "深圳"], n),
"age": rng.integers(18, 65, n),
"gender": rng.choice(["M", "F"], n),
"income": rng.normal(15000, 5000, n).round(2),
"orders": rng.poisson(3, n),
"signup_date": pd.date_range("2024-01-01", periods=n, freq="h"),
"churn": rng.choice([0, 1], n, p=[0.8, 0.2]),
})
# 初步检查
print(df.head())
print(df.info())
print(df.describe())
print(df.isna().sum())
print(df.duplicated().sum())专业习惯:
# 缺失值处理
df["income"] = df["income"].fillna(df["income"].median())
df["city"] = df["city"].fillna("未知")
# 类型转换
df["city"] = df["city"].astype("category")
df["gender"] = df["gender"].astype("category")
df["signup_date"] = pd.to_datetime(df["signup_date"])
# 异常值处理:用 IQR 截断
q1, q3 = df["income"].quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
df["income"] = df["income"].clip(lower, upper)
# 派生字段
df["signup_month"] = df["signup_date"].dt.to_period("M")
df["income_per_order"] = (df["income"] / (df["orders"] + 1)).round(2)
print(df.dtypes)
print(df.head())避免 SettingWithCopyWarning:
# 推荐:显式 copy
clean = df.copy()
clean["income"] = clean["income"].fillna(clean["income"].median())city_stats = (
df.groupby("city", observed=True)
.agg(
users=("user_id", "count"),
avg_income=("income", "mean"),
avg_orders=("orders", "mean"),
churn_rate=("churn", "mean"),
)
.sort_values("churn_rate", ascending=False)
)
print(city_stats)pivot = pd.pivot_table(
df,
index="city",
columns="gender",
values="income",
aggfunc="mean",
observed=True,
).round(2)
print(pivot)numeric_cols = ["age", "income", "orders", "churn"]
corr = df[numeric_cols].corr(method="pearson")
print(corr)monthly = (
df.set_index("signup_date")
.resample("M")
.agg(users=("user_id", "count"), churn=("churn", "mean"))
)
print(monthly)import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", font="SimHei") # 中文显示
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# 收入分布
sns.histplot(df["income"], bins=30, kde=True, ax=axes[0, 0])
axes[0, 0].set_title("收入分布")
# 城市 churn 率
sns.barplot(data=df, x="city", y="churn", ax=axes[0, 1], errorbar=None)
axes[0, 1].set_title("各城市流失率")
# 年龄与收入
sns.scatterplot(data=df, x="age", y="income", hue="churn", ax=axes[1, 0], alpha=0.6)
axes[1, 0].set_title("年龄与收入")
# 相关性热力图
sns.heatmap(corr, annot=True, cmap="coolwarm", ax=axes[1, 1])
axes[1, 1].set_title("相关性矩阵")
plt.tight_layout()
plt.savefig("eda.png", dpi=150)
plt.show()可视化原则:
from scipy import stats
# 两城市收入差异:独立样本 t 检验
bj = df.loc[df["city"] == "北京", "income"]
sh = df.loc[df["city"] == "上海", "income"]
t_stat, p_value = stats.ttest_ind(bj, sh, equal_var=False)
print(f"t={t_stat:.3f}, p={p_value:.4f}")
# 卡方检验:性别与流失是否独立
contingency = pd.crosstab(df["gender"], df["churn"])
chi2, p, dof, expected = stats.chi2_contingency(contingency)
print(f"chi2={chi2:.3f}, p={p:.4f}, dof={dof}")统计注意事项:
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score
# 特征与标签
X = df[["city", "age", "gender", "income", "orders"]]
y = df["churn"]
numeric_features = ["age", "income", "orders"]
categorical_features = ["city", "gender"]
preprocess = ColumnTransformer([
("num", StandardScaler(), numeric_features),
("cat", OneHotEncoder(handle_unknown="ignore"), categorical_features),
])
pipe = Pipeline([
("preprocess", preprocess),
("clf", RandomForestClassifier(n_estimators=200, random_state=42)),
])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
y_prob = pipe.predict_proba(X_test)[:, 1]
print(classification_report(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_prob))
# 交叉验证
cv_scores = cross_val_score(pipe, X, y, cv=5, scoring="roc_auc")
print("CV AUC:", cv_scores.mean().round(4), "+/-", cv_scores.std().round(4))建模原则:
# 不推荐:逐行循环
# df["income_per_order"] = df.apply(lambda r: r["income"] / (r["orders"] + 1), axis=1)
# 推荐:向量化
df["income_per_order"] = df["income"] / (df["orders"] + 1)import polars as pl
pl_df = pl.from_pandas(df)
result = (
pl_df.lazy()
.group_by("city")
.agg([
pl.len().alias("users"),
pl.col("income").mean().alias("avg_income"),
pl.col("churn").mean().alias("churn_rate"),
])
.sort("churn_rate", descending=True)
.collect()
)
print(result)import duckdb
duckdb.sql("""
SELECT city,
COUNT(*) AS users,
AVG(income) AS avg_income,
AVG(churn) AS churn_rate
FROM df
GROUP BY city
ORDER BY churn_rate DESC
""").show()性能建议:
category 减少内存;copy。# analysis.py
from pathlib import Path
import pandas as pd
DATA_DIR = Path("data")
OUTPUT_DIR = Path("output")
OUTPUT_DIR.mkdir(exist_ok=True)
def load_data(path: Path) -> pd.DataFrame:
return pd.read_csv(path, parse_dates=["signup_date"])
def clean(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df["income"] = df["income"].fillna(df["income"].median())
df["city"] = df["city"].astype("category")
return df
def main():
df = load_data(DATA_DIR / "users.csv")
df = clean(df)
df.to_parquet(OUTPUT_DIR / "clean.parquet", index=False)
if __name__ == "__main__":
main()可复现性清单:
pip freeze、Poetry、uv;apply 逐行处理大数据;Python 数据分析的专业能力,不在于记住多少 API,而在于建立一条从问题定义到可复现交付的完整链路。pandas 适合交互与中小规模数据,Polars 与 DuckDB 适合更大规模与 SQL 场景,scikit-learn 提供稳健的建模基线,可视化用于发现与沟通。真正高质量的分析,是在数据质量、统计严谨性、计算性能与业务语义之间持续权衡的结果。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。