从人口普查数据到收入洞察:一套可复用的 Pandas EDA 教程

你将学到什么

这篇教程不是教你画几张图,而是教你把一份原始表格数据变成可解释的收入分析结论。

完整流程分 5 步:

  1. 明确分析问题:收入是否与教育、工时、性别、职业、年龄有关?
  2. 读取数据并做基础检查。
  3. 清洗缺失值和异常标记。
  4. 用分组统计找出收入差异。
  5. 用图表或表格把结论讲清楚,同时说明局限。

场景背景

原文分析的是 UCI Adult Census Income Dataset。这个数据集来自 1990 年代美国人口普查,常用于预测一个人的年收入是否超过 50K 美元。

典型字段包括:

原文的核心结论是:收入不是由单一因素决定的。教育、职业、经验、工时、性别都可能影响收入分布,但文章主要展示的是相关性,不是因果关系。

环境准备

如果你只想跑本文的小样本统计,pandas 就够了。

python3 -m pip install pandas

如果你要复现图表,再安装:

python3 -m pip install matplotlib seaborn

注意:生产或团队项目里不要随手新增依赖,先确认项目是否已有数据分析环境。

示例 1:用小样本复现收入分析主流程

先准备一份小 CSV,模拟人口普查数据。

from io import StringIO

import pandas as pd

CSV_DATA = """age,education,workclass,occupation,sex,hours_per_week,income
39,Bachelors,Private,Exec-managerial,Male,45,>50K
28,HS-grad,Private,Handlers-cleaners,Male,40,<=50K
44,Masters,Private,Prof-specialty,Female,50,>50K
31,Some-college,Private,Sales,Female,38,<=50K
52,Doctorate,Self-emp-inc,Prof-specialty,Male,55,>50K
23,HS-grad,Private,Other-service,Female,35,<=50K
48,Prof-school,Self-emp-inc,Exec-managerial,Male,60,>50K
37,Bachelors,Private,Adm-clerical,Female,42,<=50K
"""

df = pd.read_csv(StringIO(CSV_DATA))
print(df.head())
print(df.shape)

预期输出形态:

   age     education     workclass        occupation     sex  hours_per_week income
0   39     Bachelors       Private   Exec-managerial    Male              45   >50K
...
(8, 7)

这里先看两件事:

真实项目里,这一步要补充 df.info()df.describe()df.isna().sum(),避免拿脏数据直接画图。

示例 2:清洗缺失值和异常标记

原文数据里用 ? 表示缺失值。常见做法是先替换为 NA,再删除缺失行。

from io import StringIO

import pandas as pd

CSV_DATA = """age,education,workclass,occupation,sex,hours_per_week,income
39,Bachelors,Private,Exec-managerial,Male,45,>50K
28,HS-grad,?,Handlers-cleaners,Male,40,<=50K
44,Masters,Private,Prof-specialty,Female,50,>50K
"""

df = pd.read_csv(StringIO(CSV_DATA))
clean_df = df.replace("?", pd.NA).dropna()

print("before", df.shape)
print("after", clean_df.shape)
print(clean_df)

预期输出:

before (3, 7)
after (2, 7)

原文中,数据从 32,561 行清洗到 30,162 行。这个数字很重要,因为它说明分析结论基于清洗后的样本,而不是原始全集。

示例 3:按教育水平统计高收入比例

教育是原文重点分析的变量之一。下面用 groupby 计算不同教育水平下 >50K 的占比。

from io import StringIO

import pandas as pd

CSV_DATA = """age,education,workclass,occupation,sex,hours_per_week,income
39,Bachelors,Private,Exec-managerial,Male,45,>50K
28,HS-grad,Private,Handlers-cleaners,Male,40,<=50K
44,Masters,Private,Prof-specialty,Female,50,>50K
31,Some-college,Private,Sales,Female,38,<=50K
52,Doctorate,Self-emp-inc,Prof-specialty,Male,55,>50K
23,HS-grad,Private,Other-service,Female,35,<=50K
48,Prof-school,Self-emp-inc,Exec-managerial,Male,60,>50K
37,Bachelors,Private,Adm-clerical,Female,42,<=50K
"""

df = pd.read_csv(StringIO(CSV_DATA))
summary = (
    df.assign(is_high_income=df["income"].eq(">50K"))
    .groupby("education", as_index=False)
    .agg(total=("income", "size"), high_income_rate=("is_high_income", "mean"))
    .sort_values("high_income_rate", ascending=False)
)

summary["high_income_rate"] = (summary["high_income_rate"] * 100).round(1)
print(summary.to_string(index=False))

预期输出:

   education  total  high_income_rate
   Doctorate      1             100.0
     Masters      1             100.0
 Prof-school      1             100.0
   Bachelors      2              50.0
     HS-grad      2               0.0
Some-college      1               0.0

讲解时不要把这个结论说成“读博一定高收入”。更准确的表达是:

在这个样本中,高教育水平群体的高收入比例更高;但这只是相关性,仍需结合职业、年龄、地区等变量进一步验证。

示例 4:分析工时与收入的关系

原文用箱线图说明:高收入群体通常工作时间更长,但低收入群体中也有人每周工作很久。

先用表格复现这个观察:

from io import StringIO

import pandas as pd

CSV_DATA = """age,education,workclass,occupation,sex,hours_per_week,income
39,Bachelors,Private,Exec-managerial,Male,45,>50K
28,HS-grad,Private,Handlers-cleaners,Male,40,<=50K
44,Masters,Private,Prof-specialty,Female,50,>50K
31,Some-college,Private,Sales,Female,38,<=50K
52,Doctorate,Self-emp-inc,Prof-specialty,Male,55,>50K
23,HS-grad,Private,Other-service,Female,35,<=50K
48,Prof-school,Self-emp-inc,Exec-managerial,Male,60,>50K
37,Bachelors,Private,Adm-clerical,Female,72,<=50K
"""

df = pd.read_csv(StringIO(CSV_DATA))
result = df.groupby("income")["hours_per_week"].describe()[["count", "mean", "50%", "max"]]
print(result.round(1))

预期输出形态:

        count  mean   50%   max
income
<=50K     4.0  46.2  39.0  72.0
>50K      4.0  52.5  52.5  60.0

这个结果的讲法:

示例 5:如果要画图,怎么组织代码

如果你已经安装了 Matplotlib 和 Seaborn,可以这样画教育与收入关系:

import matplotlib.pyplot as plt
import seaborn as sns

sns.countplot(data=df, x="education", hue="income")
plt.xticks(rotation=45, ha="right")
plt.title("Income by Education")
plt.tight_layout()
plt.show()

画工时箱线图:

sns.boxplot(data=df, x="income", y="hours_per_week")
plt.title("Hours per Week by Income")
plt.tight_layout()
plt.show()

图表要服务结论,不要为了画图而画图。每张图至少回答一个问题:

分享时可以这样讲

推荐按这个顺序讲:

  1. 先抛问题:收入到底由什么决定?努力、学历、职业还是运气?
  2. 再给数据:使用 1994 年美国人口普查 Adult 数据集。
  3. 讲清洗:原始 32,561 行,清洗后 30,162 行,缺失值用 ? 标记。
  4. 讲发现:教育、工时、性别、职业、年龄都与收入分布有关。
  5. 讲边界:这些是相关性,不是因果结论;数据也不代表今天的就业市场。
  6. 讲迁移:这个流程可以复用到员工薪酬、用户分层、客户价值、销售线索评分等场景。

可迁移到业务分析的模板

你可以把这套 EDA 方法迁移到任何分层问题:

通用模板如下:

import pandas as pd

TARGET_COLUMN = "income"
GROUP_COLUMN = "education"
POSITIVE_LABEL = ">50K"

df = pd.read_csv("your_data.csv")
clean_df = df.replace("?", pd.NA).dropna()

analysis = (
    clean_df.assign(is_positive=clean_df[TARGET_COLUMN].eq(POSITIVE_LABEL))
    .groupby(GROUP_COLUMN, as_index=False)
    .agg(total=(TARGET_COLUMN, "size"), positive_rate=("is_positive", "mean"))
    .sort_values("positive_rate", ascending=False)
)

analysis["positive_rate"] = (analysis["positive_rate"] * 100).round(2)
print(analysis)

TARGET_COLUMNGROUP_COLUMNPOSITIVE_LABEL 换掉,就能复用到其他二分类分析。

常见坑

练习任务

  1. 换成你自己的 CSV,找一个二分类目标字段。
  2. 选 3 个分组字段,例如部门、地区、岗位、客户等级。
  3. 分别计算正样本比例。
  4. 找出一个“人数多但正样本率不高”的群体。
  5. 写一句谨慎结论,必须包含“相关性,不代表因果”。

总结

这篇原文的价值不在于“教育一定带来高收入”这样的结论,而在于展示了一套简单、可复制的数据分析路径:

先清洗数据,再分组统计,然后用图表验证直觉,最后明确结论边界。

这套方法适合数据分析入门,也适合在业务场景里快速做第一轮探索。真正需要决策时,还要进一步加入多变量建模、时间变化、样本偏差检查和业务专家复核。