第一个 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 完成一个完整的数据分析流程!
场景:分析学生调查数据
假设我们有一份学生调查数据:
| name | age | major | gpa | study_hours |
|---|---|---|---|---|
| Alice | 20 | Economics | 3.8 | 25 |
| Bob | 22 | Sociology | 3.5 | 20 |
| Carol | 21 | Political Science | 3.9 | 30 |
| David | 23 | Economics | 3.2 | 15 |
完整代码(可以直接运行)
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 库,并简称为pdpd.DataFrame():创建数据框(类似 Stata 的 dataset,R 的 data.frame)
3. 查看数据
python
print(df)对比:
- Stata:
browse或list - 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")这个案例展示了什么?
- 数据质量检查:类似学术论文的数据清洗流程
- 描述性统计:按组计算均值、中位数、分位数
- 不平等指标:计算基尼系数(Stata 中需要安装
ineqdeco) - 回归分析:包含二次项、类别变量、交互项
- 发表级可视化:4 个子图,300 DPI 输出
- 结果导出:可直接用于论文
实战练习
练习 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:新的分析
计算:
- 每个专业的平均学习时长
- GPA 高于 3.6 的学生有哪些?
- 年龄与 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 编程哲学
- Python 的核心是对象:
df是一个对象,.describe()是它的方法 - 链式调用:
df.groupby('major')['gpa'].mean()是链式调用 - 导入库:
import pandas as pd是标准做法 - 索引方式:
df['column']或df[['col1', 'col2']]
Python vs Stata/R:思维方式对比
| 方面 | Stata | R | Python |
|---|---|---|---|
| 数据框 | 全局唯一 | 可以多个,用 $ 访问列 | 可以多个,用 [] 访问列 |
| 函数调用 | command varlist | function(data$var) | df['var'].method() |
| 赋值 | gen, replace | <- 或 = | = |
| 管道操作 | 不支持 | %>% (dplyr) | . (方法链) |
| 向量化 | 自动 | 自动 | 需要使用 NumPy |
最佳实践
- 代码组织:用注释分隔功能模块(
# ========== 1. 数据加载 ==========) - 变量命名:使用有意义的名字(
df_clean而不是df2) - 错误处理:养成检查数据质量的习惯(缺失值、异常值)
- 可复现性:设置随机种子(
np.random.seed(42)) - 性能优化:大数据用
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 程序!在下一个模块中,我们将:
- 学习如何配置 Python 开发环境
- 了解 Jupyter Notebook 的使用
- 掌握 VS Code 的 Python 配置
你已经掌握了:
- Python 的基本语法
- Pandas 数据框的概念
- 描述性统计和分组操作
- 与 Stata/R 的思维对比
准备好进入下一阶段了吗?