Skip to content

第一个 Python 程序

从 "Hello World" 到数据分析 —— 5 分钟体验 Python


传统的第一个程序:Hello World

Stata 版本

stata
display "Hello World"

R 版本

r
print("Hello World")

Python 版本

python
print("Hello World")

运行结果

Hello World

更有意义的第一个程序:数据分析

让我们用 Python 完成一个完整的数据分析流程!

场景:分析学生调查数据

假设我们有一份学生调查数据:

nameagemajorgpastudy_hours
Alice20Economics3.825
Bob22Sociology3.520
Carol21Political Science3.930
David23Economics3.215

完整代码(可以直接运行)

python
# 步骤 1: 创建数据
data = {
    'name': ['Alice', 'Bob', 'Carol', 'David', 'Emma'],
    'age': [20, 22, 21, 23, 20],
    'major': ['Economics', 'Sociology', 'Political Science', 'Economics', 'Sociology'],
    'gpa': [3.8, 3.5, 3.9, 3.2, 3.7],
    'study_hours': [25, 20, 30, 15, 22]
}

# 步骤 2: 创建数据框(类似 Stata 的 dataset)
import pandas as pd
df = pd.DataFrame(data)

# 步骤 3: 查看数据
print(" 数据预览:")
print(df)

# 步骤 4: 描述性统计
print("\n 描述性统计:")
print(df[['age', 'gpa', 'study_hours']].describe())

# 步骤 5: 按专业分组统计
print("\n 按专业统计平均 GPA:")
print(df.groupby('major')['gpa'].mean())

# 步骤 6: 简单可视化(GPA 与学习时长的关系)
import matplotlib.pyplot as plt
plt.scatter(df['study_hours'], df['gpa'])
plt.xlabel('Study Hours per Week')
plt.ylabel('GPA')
plt.title('GPA vs Study Hours')
plt.show()

运行结果

 数据预览:
    name  age               major  gpa  study_hours
0  Alice   20           Economics  3.8           25
1    Bob   22           Sociology  3.5           20
2  Carol   21  Political Science  3.9           30
3  David   23           Economics  3.2           15
4   Emma   20           Sociology  3.7           22

 描述性统计:
             age       gpa  study_hours
count   5.000000  5.000000     5.000000
mean   21.200000  3.620000    22.400000
std     1.303840  0.262488     5.549775
min    20.000000  3.200000    15.000000
25%    20.000000  3.500000    20.000000
50%    21.000000  3.700000    22.000000
75%    22.000000  3.800000    25.000000
max    23.000000  3.9000000    30.000000

 按专业统计平均 GPA:
major
Economics            3.50
Political Science    3.90
Sociology            3.60
Name: gpa, dtype: float64

代码解析

1. 创建数据(字典)

python
data = {
    'name': ['Alice', 'Bob', 'Carol', 'David', 'Emma'],
    'age': [20, 22, 21, 23, 20]
}

理解

  • {} 表示字典(dictionary)
  • 'name': [...] 表示键值对
  • 类似于 R 的 list(name = c("Alice", "Bob", ...))

2. 创建数据框

python
import pandas as pd
df = pd.DataFrame(data)

理解

  • import pandas as pd:导入 Pandas 库,并简称为 pd
  • pd.DataFrame():创建数据框(类似 Stata 的 dataset,R 的 data.frame)

3. 查看数据

python
print(df)

对比

  • Stata: browselist
  • R: print(df) 或直接 df
  • Python: print(df)df(在 Jupyter 中)

4. 描述性统计

python
df[['age', 'gpa', 'study_hours']].describe()

对比

  • Stata: summarize age gpa study_hours
  • R: summary(df[c("age", "gpa", "study_hours")])
  • Python: df[['age', 'gpa', 'study_hours']].describe()

5. 分组统计

python
df.groupby('major')['gpa'].mean()

对比

  • Stata: tabstat gpa, by(major)
  • R: aggregate(gpa ~ major, data=df, FUN=mean)
  • Python: df.groupby('major')['gpa'].mean()

可视化示例

散点图:GPA vs Study Hours

python
import matplotlib.pyplot as plt

plt.scatter(df['study_hours'], df['gpa'])
plt.xlabel('Study Hours per Week')
plt.ylabel('GPA')
plt.title('GPA vs Study Hours')
plt.show()

对比 Stata

stata
twoway scatter gpa study_hours, title("GPA vs Study Hours")

对比 R

r
plot(df$study_hours, df$gpa,
     xlab="Study Hours", ylab="GPA",
     main="GPA vs Study Hours")

进阶示例:添加回归线

python
import numpy as np
from scipy import stats

# 计算回归线
slope, intercept, r_value, p_value, std_err = stats.linregress(df['study_hours'], df['gpa'])
line = slope * df['study_hours'] + intercept

# 绘图
plt.scatter(df['study_hours'], df['gpa'], label='Actual Data')
plt.plot(df['study_hours'], line, color='red', label=f'Regression Line (R²={r_value**2:.3f})')
plt.xlabel('Study Hours per Week')
plt.ylabel('GPA')
plt.title('GPA vs Study Hours with Regression Line')
plt.legend()
plt.show()

print(f" 回归结果:GPA = {intercept:.3f} + {slope:.3f} * Study Hours")
print(f"   R² = {r_value**2:.3f}, p-value = {p_value:.4f}")

运行结果

 回归结果:GPA = 2.954 + 0.030 * Study Hours
   R² = 0.523, p-value = 0.1678

完整的数据分析模板

python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# ========== 1. 数据加载 ==========
# 方式 1: 从字典创建
data = {
    'variable1': [1, 2, 3, 4, 5],
    'variable2': [10, 20, 30, 40, 50]
}
df = pd.DataFrame(data)

# 方式 2: 从 CSV 文件加载(更常用)
# df = pd.read_csv('data.csv')

# ========== 2. 数据清洗 ==========
df = df.dropna()  # 删除缺失值
df = df[df['variable1'] > 0]  # 筛选条件

# ========== 3. 创建新变量 ==========
df['log_var1'] = np.log(df['variable1'])
df['var1_squared'] = df['variable1'] ** 2

# ========== 4. 描述性统计 ==========
print(df.describe())
print(df.groupby('category')['variable1'].mean())

# ========== 5. 可视化 ==========
plt.hist(df['variable1'], bins=10)
plt.title('Distribution of Variable 1')
plt.show()

# ========== 6. 统计分析 ==========
# 相关系数
correlation = df['variable1'].corr(df['variable2'])
print(f"Correlation: {correlation:.3f}")

# 线性回归
slope, intercept, r_value, p_value, std_err = stats.linregress(df['variable1'], df['variable2'])
print(f"Regression: y = {intercept:.2f} + {slope:.2f}x, R² = {r_value**2:.3f}")

# ========== 7. 保存结果 ==========
df.to_csv('output.csv', index=False)

进阶案例:从真实数据到发表级分析

案例:使用真实数据分析收入不平等

python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

# 生成模拟的收入分布数据(模拟 CPS 数据)
np.random.seed(42)
n = 5000

# 生成不同教育水平的收入
education_levels = ['High School', 'Bachelor', 'Master', 'PhD']
education_weights = [0.4, 0.35, 0.20, 0.05]

data = {
    'person_id': range(1, n+1),
    'age': np.random.randint(22, 65, n),
    'education': np.random.choice(education_levels, n, p=education_weights),
    'experience': np.random.randint(0, 40, n),
    'female': np.random.choice([0, 1], n),
    'urban': np.random.choice([0, 1], n, p=[0.3, 0.7])
}

df = pd.DataFrame(data)

# 根据特征生成收入(对数正态分布)
education_premium = df['education'].map({
    'High School': 0,
    'Bachelor': 0.3,
    'Master': 0.5,
    'PhD': 0.7
})

log_income = (10.5 +
              education_premium +
              0.03 * df['age'] -
              0.0004 * df['age']**2 +
              0.02 * df['experience'] -
              0.15 * df['female'] +
              0.10 * df['urban'] +
              np.random.normal(0, 0.3, n))

df['income'] = np.exp(log_income)

# ========== 1. 数据质量检查 ==========
print(" 数据质量报告")
print("=" * 50)
print(f"总样本量: {len(df)}")
print(f"缺失值数量: {df.isnull().sum().sum()}")
print(f"\n收入分布:")
print(df['income'].describe())

# ========== 2. 描述性统计 ==========
print("\n 按教育水平分组的收入统计")
print("=" * 50)
summary = df.groupby('education')['income'].agg([
    ('Count', 'count'),
    ('Mean', 'mean'),
    ('Median', 'median'),
    ('Std', 'std'),
    ('P25', lambda x: x.quantile(0.25)),
    ('P75', lambda x: x.quantile(0.75))
]).round(0)
print(summary)

# ========== 3. 不平等指标计算 ==========
def gini_coefficient(x):
    """计算基尼系数"""
    x = np.sort(x)
    n = len(x)
    cumsum = np.cumsum(x)
    return (2 * np.sum((n - np.arange(1, n+1) + 0.5) * x)) / (n * np.sum(x)) - 1

gini = gini_coefficient(df['income'])
print(f"\n 收入基尼系数: {gini:.3f}")

# 计算不同教育组之间的收入比
mean_income = df.groupby('education')['income'].mean()
college_premium = (mean_income['Bachelor'] / mean_income['High School'] - 1) * 100
print(f"大学溢价 (Bachelor vs High School): {college_premium:.1f}%")

# ========== 4. 回归分析 ==========
import statsmodels.formula.api as smf

# OLS 回归
model = smf.ols('np.log(income) ~ C(education) + age + I(age**2) + experience + female + urban',
                data=df).fit()

print("\n 回归分析结果")
print("=" * 50)
print(model.summary().tables[1])

# 提取关键系数
edu_coef = model.params['C(education)[T.Bachelor]']
female_coef = model.params['female']

print(f"\n关键发现:")
print(f"- 大学教育使收入提高 {(np.exp(edu_coef)-1)*100:.1f}%")
print(f"- 性别工资差距: {abs(female_coef)*100:.1f}% (对数点)")

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

# 子图1:收入分布(对数尺度)
axes[0, 0].hist(np.log(df['income']), bins=50, edgecolor='black', alpha=0.7)
axes[0, 0].set_xlabel('Log(Income)')
axes[0, 0].set_ylabel('Frequency')
axes[0, 0].set_title('Income Distribution (Log Scale)')

# 子图2:按教育水平的收入箱线图
df.boxplot(column='income', by='education', ax=axes[0, 1])
axes[0, 1].set_ylabel('Income ($)')
axes[0, 1].set_title('Income by Education Level')
axes[0, 1].get_figure().suptitle('')  # 移除默认标题

# 子图3:年龄-收入关系
sns.scatterplot(data=df.sample(500), x='age', y='income',
                hue='education', alpha=0.6, ax=axes[1, 0])
axes[1, 0].set_ylabel('Income ($)')
axes[1, 0].set_title('Income vs Age by Education')

# 子图4:性别工资差距
gender_income = df.groupby(['education', 'female'])['income'].mean().unstack()
gender_income.plot(kind='bar', ax=axes[1, 1])
axes[1, 1].set_ylabel('Mean Income ($)')
axes[1, 1].set_title('Gender Pay Gap by Education')
axes[1, 1].set_xticklabels(axes[1, 1].get_xticklabels(), rotation=45)
axes[1, 1].legend(['Male', 'Female'])

plt.tight_layout()
plt.savefig('income_analysis.png', dpi=300, bbox_inches='tight')
print("\n 图表已保存为 'income_analysis.png'")

# ========== 6. 导出结果 ==========
# 导出回归结果表格
with open('regression_results.txt', 'w') as f:
    f.write(str(model.summary()))

# 导出描述性统计
summary.to_csv('descriptive_stats.csv')

print("\n 分析完成!生成文件:")
print("  - regression_results.txt")
print("  - descriptive_stats.csv")
print("  - income_analysis.png")

这个案例展示了什么?

  1. 数据质量检查:类似学术论文的数据清洗流程
  2. 描述性统计:按组计算均值、中位数、分位数
  3. 不平等指标:计算基尼系数(Stata 中需要安装 ineqdeco
  4. 回归分析:包含二次项、类别变量、交互项
  5. 发表级可视化:4 个子图,300 DPI 输出
  6. 结果导出:可直接用于论文

实战练习

练习 1:修改数据

尝试修改上面的学生数据,添加一个新学生:

  • 名字:Frank
  • 年龄:24
  • 专业:Economics
  • GPA:3.6
  • 学习时长:28

提示:使用 pd.concat()df.loc[]

点击查看答案
python
# 方法 1:使用 pd.concat
new_student = pd.DataFrame({
    'name': ['Frank'],
    'age': [24],
    'major': ['Economics'],
    'gpa': [3.6],
    'study_hours': [28]
})
df = pd.concat([df, new_student], ignore_index=True)

# 方法 2:使用 loc
df.loc[len(df)] = ['Frank', 24, 'Economics', 3.6, 28]

练习 2:新的分析

计算:

  1. 每个专业的平均学习时长
  2. GPA 高于 3.6 的学生有哪些?
  3. 年龄与 GPA 的相关系数
点击查看答案
python
# 1. 每个专业的平均学习时长
print(df.groupby('major')['study_hours'].mean())

# 2. GPA 高于 3.6 的学生
high_gpa = df[df['gpa'] > 3.6]
print(high_gpa[['name', 'gpa']])

# 3. 年龄与 GPA 的相关系数
correlation = df['age'].corr(df['gpa'])
print(f"Correlation: {correlation:.3f}")

练习 3:复刻 Stata 的 tabstat

用 Python 复刻 Stata 的 tabstat income education, by(gender) stat(mean sd min max n)

点击查看答案
python
result = df.groupby('female').agg({
    'income': ['mean', 'std', 'min', 'max', 'count'],
    'education': ['mean', 'std', 'min', 'max', 'count']
})
print(result)

关键要点

Python 编程哲学

  1. Python 的核心是对象df 是一个对象,.describe() 是它的方法
  2. 链式调用df.groupby('major')['gpa'].mean() 是链式调用
  3. 导入库import pandas as pd 是标准做法
  4. 索引方式df['column']df[['col1', 'col2']]

Python vs Stata/R:思维方式对比

方面StataRPython
数据框全局唯一可以多个,用 $ 访问列可以多个,用 [] 访问列
函数调用command varlistfunction(data$var)df['var'].method()
赋值gen, replace<-==
管道操作不支持%>% (dplyr). (方法链)
向量化自动自动需要使用 NumPy

最佳实践

  1. 代码组织:用注释分隔功能模块(# ========== 1. 数据加载 ==========
  2. 变量命名:使用有意义的名字(df_clean 而不是 df2
  3. 错误处理:养成检查数据质量的习惯(缺失值、异常值)
  4. 可复现性:设置随机种子(np.random.seed(42)
  5. 性能优化:大数据用 pd.read_csv(chunksize=1000) 分块读取

从第一个程序到生产级代码

初学者版本

python
df = pd.read_csv("data.csv")
print(df.mean())

生产级版本

python
import pandas as pd
import logging

# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def load_and_validate_data(filepath):
    """
    加载并验证数据

    Parameters:
    -----------
    filepath : str
        数据文件路径

    Returns:
    --------
    pd.DataFrame
        清洗后的数据框
    """
    try:
        df = pd.read_csv(filepath)
        logger.info(f"成功加载 {len(df)} 行数据")

        # 数据验证
        required_columns = ['income', 'education', 'age']
        missing_cols = set(required_columns) - set(df.columns)
        if missing_cols:
            raise ValueError(f"缺少必需的列: {missing_cols}")

        # 删除缺失值
        initial_rows = len(df)
        df = df.dropna(subset=required_columns)
        dropped_rows = initial_rows - len(df)
        if dropped_rows > 0:
            logger.warning(f"删除了 {dropped_rows} 行缺失数据")

        return df

    except FileNotFoundError:
        logger.error(f"文件不存在: {filepath}")
        raise
    except Exception as e:
        logger.error(f"加载数据时出错: {str(e)}")
        raise

# 使用函数
df = load_and_validate_data("data.csv")

差异

  • 错误处理(try-except)
  • 文档字符串(docstring)
  • 日志记录(logging)
  • 数据验证
  • 函数封装

下一步

恭喜你完成了第一个 Python 程序!在下一个模块中,我们将:

  1. 学习如何配置 Python 开发环境
  2. 了解 Jupyter Notebook 的使用
  3. 掌握 VS Code 的 Python 配置

你已经掌握了

  • Python 的基本语法
  • Pandas 数据框的概念
  • 描述性统计和分组操作
  • 与 Stata/R 的思维对比

准备好进入下一阶段了吗?

基于 MIT 许可证发布。内容版权归作者所有。