Back to skills
SKILL.md
Data Pipeline
ASecurityEnd-to-end data pipeline for empirical research: fetch economic data from APIs (FRED, World Bank, IMF, BLS, OECD, Yahoo Finance), clean and transform raw data, construct strategy-specific variables, and validate panel structure. Use when asked to fetch data, download data, clean data, merge datasets, prepare analysis-ready data.
- 8 stars
- 0 votes
- 0 copies
- 0 views
- Added September 1, 2026
Works with
Security analysis
96/100- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add zhouziyue233/great-econometrics --skill data-pipeline --agent claude-codeAre you the author of Data Pipeline?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/zhouziyue233-data-pipeline)---
name: data-pipeline
description: "End-to-end data pipeline for empirical research: fetch economic data from APIs (FRED, World Bank, IMF, BLS, OECD, Yahoo Finance), clean and transform raw data, construct strategy-specific variables, and validate panel structure. Use when asked to fetch data, download data, clean data, merge datasets, prepare analysis-ready data."
---
# Data-Pipeline
## Purpose
本 skill 是一体化数据管道,由 `/data` 命令调用,负责完整的数据工作流:
1. **数据获取**(Part 1)— 从多个 API 自动下载原始数据
2. **数据清洗与转换**(Part 2)— 合并、去重、缺失值处理、异常值检测、变量构建
3. **质量保证**(Part 3)— 验证面板结构、进行质量审计、输出分析就绪的数据集
产出:`data/clean/[project_name]_clean.[dta|parquet|csv]` + `data/clean/cleaning_log.md`
---
## When to Use
- 由 `/data` 命令自动调用(主要场景,分别在 Step 1 和 Step 2 调用)
- 独立触发:用户需要获取 + 清洗数据的完整流程
---
## Part 1: Data Acquisition(数据获取)
### Step 0: API Key Setup Check
**执行任何代码前必须先检查所需 API Key。**
1. 读取 `[plugin_root]/.env` 文件(与 `CLAUDE.md` 同目录,即插件根目录)
2. 检查 `FRED_API_KEY` 和 `BLS_API_KEY`
**若 `FRED_API_KEY` 缺失或为空:**
- 告知用户:*"需要免费的 FRED API Key。请访问 https://fred.stlouisfed.org/docs/api/api_key.html(约 1 分钟)。粘贴 Key 后我会保存。"*
- 等待输入,然后将 `FRED_API_KEY=<value>` 追加到 `.env`
**若 `BLS_API_KEY` 缺失:**
- 告知用户这是可选但可提升速率限制的 Key,地址 https://www.bls.gov/developers/
**若 `.env` 已存在且 Key 已设置:**
- 静默加载,通过 `python-dotenv` 注入所有代码中
```python
from dotenv import load_dotenv
load_dotenv() # 自动从 CWD 及父目录搜索 .env
```
> **⚠️ `.env` 格式要求:** 标准 `KEY=VALUE` 格式(每行一个),**非 JSON**。若为 JSON 格式,需先转换。
---
### Step 1: 接收调用上下文
**本 skill 由 `/data` 传入结构化上下文,无需重新询问用户。**
接收参数:
| 参数 | 说明 | 示例 |
|------|------|------|
| `target_datasets` | 目标数据集名称列表 | `["FRED宏观", "World Bank"]` |
| `variables` | 变量清单 | `Y=gdp_growth, Z=tariff_rate` |
| `time_range` | 起止年份与频率 | `2000–2023, 年度` |
| `geo_scope` | 地理范围 | `中国31省` / `美国全国` |
| `identification_strategy` | 识别策略 | `DiD` / `RDD` / `IV` / `Panel FE` |
| `output_format` | 输出格式 | `csv`(默认)/ `dta` / `parquet` |
---
### Step 2: 选择合适的数据源
根据传入的数据集名称和地理范围,选择对应 API:
| 数据类型 | 最佳数据源 | 方式 |
|---------|----------|------|
| 美国宏观时间序列 | FRED | `fredapi` |
| 全球发展指标 | World Bank | `wbdata` |
| 美国劳动力市场 | BLS API v2 | `requests`(需分块) |
| 跨国宏观/金融 | IMF WEO | `imf-reader` |
| 金融资产价格 | Yahoo Finance | `yfinance` |
> **无法自动获取的数据源(需用户手动下载):**
> - **OECD**:数据端点受 Cloudflare 防护,沙箱不可访问。优先用 IMF WEO 替代。
> - **国家统计局(NBS)**:API 限制中国大陆 IP。请前往 https://data.stats.gov.cn 下载后走手动上传流程。
---
### Step 3: 生成并执行获取代码
生成的脚本必须包含:
**① 基础要素**
- API Key 通过环境变量注入(`python-dotenv`),不硬编码
- API 请求失败的错误处理(捕获异常、打印错误信息)
- 系列 ID 的中英文注释说明
**② 输出路径规范**
```python
from datetime import date
filename = f"data/raw/{source_name}_{date.today().strftime('%Y%m%d')}.csv"
df.to_csv(filename, index=False)
```
**③ 写入 `data/raw/data_log.md`**
```python
log_entry = f"""
## {dataset_name} 获取记录
- 获取日期:{date.today()}
- 数据来源:{source_name}
- 原始文件路径:{filename}
- 变量数:{df.shape[1]},观测数:{df.shape[0]}
- 频率:{frequency}
- 合并键:{merge_key}
"""
os.makedirs("data/raw", exist_ok=True)
with open("data/raw/data_log.md", "a", encoding="utf-8") as f:
f.write(log_entry)
```
**④ 关键变量验证**
```python
required_vars = [Y_var, D_var, Z_var] + control_vars
available = df.columns.tolist()
found = [v for v in required_vars if v in available]
missing = [v for v in required_vars if v not in available]
print("=== 变量到位检查 ===")
for v in found:
print(f"✅ {v}")
for v in missing:
print(f"⚠️ {v} — 需在清洗阶段处理")
```
---
## Part 2: Data Cleaning & Transformation(数据清洗与转换)
### Step 0: 接收调用上下文
接收参数(来自 `identification-memo.md`):
| 参数 | 说明 | 示例 |
|------|------|------|
| `identification_strategy` | 识别策略 | `DiD` / `RDD` / `IV` |
| `Y_var` | 结果变量 | `log_gdp_growth` |
| `D_var` | 处理变量 | `policy_dummy` |
| `Z_var` | 识别变量 | `tariff_rate_1990` |
| `control_vars` | 协变量列表 | `["log_gdp", "population"]` |
| `id_var` | 面板个体标识 | `province_code` |
| `time_var` | 时间变量 | `year` |
| `input_files` | 原始数据文件路径 | `["data/raw/fred_*.csv"]` |
| `merge_plan` | 多源合并方案 | 主文件 + 辅助文件 |
---
### Step 1: 多源数据合并(条件触发)
**触发条件:** `input_files` 包含 ≥2 个文件且 `merge_plan` 已确认
#### 1.1 执行合并
```python
import pandas as pd
df = pd.read_csv("data/raw/[主文件]")
n_before = len(df)
for aux_file, merge_key, how in merge_plan:
aux = pd.read_csv(f"data/raw/{aux_file}")
df = df.merge(aux, on=merge_key, how=how, suffixes=("", "_aux"))
print(f"合并 {aux_file}:{len(df):,} 行({how} join on {merge_key})")
```
#### 1.2 合并质量报告
```python
n_after = len(df)
match_rate = n_after / n_before * 100
print(f"匹配率:{match_rate:.1f}%")
dups = df.duplicated(subset=merge_key).sum()
if dups > 0:
print(f"⚠️ 发现 {dups} 行重复")
for v in [Y_var, D_var, Z_var]:
miss_rate = df[v].isna().mean() * 100
print(f"{v} 缺失率:{miss_rate:.1f}%")
```
若匹配率低于 70%,需向用户报告并说明原因。
---
### Step 2: 通用清洗流水线
按以下顺序执行,**每步都在 `cleaning_log.md` 中记录样本量和关键指标变化**。
#### 2.1 重复观测检测与处理
```python
dup_mask = df.duplicated(subset=[id_var, time_var], keep=False)
n_dups = dup_mask.sum()
print(f"重复观测:{n_dups} 行")
if n_dups > 0:
df_dups = df[dup_mask]
df_dups.to_csv("data/clean/duplicates_removed.csv", index=False)
df = df.drop_duplicates(subset=[id_var, time_var], keep="first")
```
#### 2.2 缺失值编码识别与处理
```python
MISSING_CODES = [-99, -88, -77, -9, 9999, 99999, -9999]
df.replace(MISSING_CODES, pd.NA, inplace=True)
miss_report = df.isnull().mean().sort_values(ascending=False)
miss_high = miss_report[miss_report > 0.05]
print("缺失率 > 5% 的变量:")
for var, rate in miss_high.items():
print(f" {var}: {rate:.1%}")
```
**缺失值处理决策**:
| 情形 | 推荐处理 |
|------|---------|
| Y / D 缺失 | 明确报告,删除;不得插补 |
| Z 缺失 | 明确报告;可能非随机 |
| 协变量 < 5% | 可考虑均值/中位数插补 |
| 协变量 5–20% | 建议多重插补或敏感性分析 |
| 协变量 > 20% | 寻找替代变量 |
#### 2.3 变量类型修正与命名规范化
```python
# 列名统一为 snake_case
df.columns = (
df.columns
.str.strip()
.str.lower()
.str.replace(r"\s+", "_", regex=True)
.str.replace(r"[^a-z0-9_]", "", regex=True)
)
# 关键变量类型修正
df[time_var] = pd.to_numeric(df[time_var], errors="coerce").astype("Int64")
df[id_var] = df[id_var].astype(str).str.strip()
for col in numeric_vars:
df[col] = pd.to_numeric(df[col], errors="coerce")
```
#### 2.4 异常值检测与处理
```python
def winsorize(series, lo=0.01, hi=0.99):
"""将连续变量在 lo/hi 分位数处缩尾"""
lo_val = series.quantile(lo)
hi_val = series.quantile(hi)
return series.clip(lo_val, hi_val)
# 仅对连续型协变量缩尾,不对 Y / D / Z 自动缩尾
for col in continuous_controls:
n_outliers = ((df[col] < df[col].quantile(0.01)) |
(df[col] > df[col].quantile(0.99))).sum()
print(f"{col}: {n_outliers} 个观测超出 [p1, p99]")
df[f"{col}_w"] = winsorize(df[col]) # 保留原始变量
```
> ⚠️ **经济学提醒**:Winsorize 需要经济学判断,不是默认必须步骤。
#### 2.5 变量标签(Stata 输出时)
```stata
label variable `Y_var' "结果变量:[描述]"
label variable `D_var' "处理变量:[描述]"
label variable `Z_var' "识别变量:[描述]"
```
---
### Step 3: 识别策略专属变量构建
根据传入的 `identification_strategy`,**自动触发**对应的变量构建模块。
---
#### 策略 A: 双重差分(DiD / Event Study)
```stata
* DiD 核心变量构建
gen treated = (group_var == "treated_group")
gen post = (year >= policy_year)
gen treated_post = treated * post
gen event_time = year - policy_year
* 交错处理时点
bysort id: egen first_treated_year = min(cond(treated_post == 1, year, .))
gen relative_time = year - first_treated_year
```
**Python 版本:**
```python
df["treated"] = (df["group_var"] == "treated_group").astype(int)
df["post"] = (df[time_var] >= policy_year).astype(int)
df["treated_post"] = df["treated"] * df["post"]
df["event_time"] = df[time_var] - policy_year
first_treated = (df[df["treated_post"] == 1]
.groupby(id_var)[time_var].min()
.rename("first_treated_year"))
df = df.merge(first_treated, on=id_var, how="left")
df["relative_time"] = df[time_var] - df["first_treated_year"]
```
---
#### 策略 B: 断点回归(RDD)
```stata
gen running_centered = running_var - cutoff_value
gen above_cutoff = (running_var >= cutoff_value)
gen running_above = running_centered * above_cutoff
```
**Python 版本:**
```python
df["running_centered"] = df["running_var"] - cutoff_value
df["above_cutoff"] = (df["running_var"] >= cutoff_value).astype(int)
df["running_above"] = df["running_centered"] * df["above_cutoff"]
print(f"阈值以上样本:{df['above_cutoff'].sum()}")
```
---
#### 策略 C: 工具变量(IV)
```python
# 范式 1:历史值作工具变量
df["Z_historical"] = df["D_var_baseyear"]
# 范式 2:Bartik 工具
df["Z_bartik"] = (df["industry_share_base"] * df["national_growth"]).groupby(id_var).transform("sum")
# 范式 3:政策 × 时间外生变动
df["Z_policy_interact"] = df["policy_var"] * df["exogenous_shock"]
# 初步检查:Z 与 D 的相关性
corr = df[[Z_var, D_var]].corr().iloc[0, 1]
print(f"Z-D 相关系数:{corr:.3f}")
if abs(corr) < 0.1:
print("⚠️ 相关性较弱,一阶段 F 可能 < 10")
```
---
#### 策略 D: 面板固定效应(Panel FE)
```python
# 唯一标识符验证
assert not df.duplicated(subset=[id_var, time_var]).any()
# 面板平衡性检查
counts = df.groupby(id_var)[time_var].count()
is_balanced = (counts == counts.max()).all()
print(f"面板平衡性:{'强平衡' if is_balanced else '弱平衡'}")
# Within / Between 方差分解
df["Y_mean_i"] = df.groupby(id_var)[Y_var].transform("mean")
df["Y_within"] = df[Y_var] - df["Y_mean_i"]
var_within = df["Y_within"].var()
var_between = df["Y_mean_i"].var()
print(f"Within 方差占比:{var_within / (var_within + var_between):.1%}")
```
---
#### 策略 E: 合成控制(Synthetic Control)
```python
assert id_var in df.columns and time_var in df.columns
df["is_treated_unit"] = (df[id_var] == treated_unit_id).astype(int)
df["is_donor"] = (1 - df["is_treated_unit"])
df["pre_treatment"] = (df[time_var] < treatment_year).astype(int)
df["post_treatment"] = (df[time_var] >= treatment_year).astype(int)
n_donors = df[df["is_donor"] == 1][id_var].nunique()
T_pre = df[df["pre_treatment"] == 1][time_var].nunique()
print(f"控制池规模:{n_donors},预处理期:{T_pre}")
if T_pre < 5:
print("⚠️ 预处理期较短,匹配质量可能受限")
```
---
### Step 4: 面板结构验证(条件触发)
**触发条件:** `identification_strategy` 为 DiD、Panel FE 或 Synthetic Control
```python
print("=== 面板结构验证 ===")
print(f"观测总数:{len(df):,}")
print(f"个体数:{df[id_var].nunique()}")
print(f"时间期数:{df[time_var].nunique()}")
print(f"时间范围:{df[time_var].min()} – {df[time_var].max()}")
obs_per_unit = df.groupby(id_var).size()
print(f"每个体观测数:min={obs_per_unit.min()}, max={obs_per_unit.max()}")
if obs_per_unit.min() == obs_per_unit.max():
print("✅ 强平衡面板")
else:
print(f"⚠️ 不平衡面板")
```
---
## Part 3: Quality Assurance(质量保证)
### Step 1: 关键变量最终审计
```python
print("\n=== 关键变量最终状态 ===")
audit_vars = [Y_var, D_var, Z_var] + control_vars
for v in audit_vars:
if v not in df.columns:
print(f"❌ {v}:不存在!")
continue
miss = df[v].isna().mean()
mean = df[v].mean() if df[v].dtype != object else "N/A"
std = df[v].std() if df[v].dtype != object else "N/A"
print(f"{'✅' if miss < 0.05 else '⚠️ '} {v}: 缺失={miss:.1%}, 均值={mean:.4g}, 标准差={std:.4g}")
```
---
### Step 2: 保存清洗后数据集
```python
import os
from datetime import date
os.makedirs("data/clean", exist_ok=True)
output_path_parquet = f"data/clean/{project_name}_clean.parquet"
df.to_parquet(output_path_parquet, index=False)
print(f"✅ 已保存:{output_path_parquet}({len(df):,} 行 × {df.shape[1]} 列)")
# 如需 Stata 格式
try:
import pyreadstat
output_path_dta = f"data/clean/{project_name}_clean.dta"
pyreadstat.write_dta(df, output_path_dta)
print(f"✅ 已保存:{output_path_dta}")
except ImportError:
print("(如需 .dta 格式,请安装 pyreadstat)")
```
---
### Step 3: 写入 `cleaning_log.md`
每次清洗完成后自动追加:
```python
log = f"""
## 数据清洗记录 — {project_name}
- 清洗日期:{date.today()}
- 识别策略:{identification_strategy}
- 输入文件:{input_files}
- 输出文件:data/clean/{project_name}_clean.parquet
### 样本量变化
| 步骤 | 操作 | 样本量 |
|------|------|--------|
| 原始数据(合并后) | — | {n_raw:,} |
| 删除重复观测 | ({id_var}, {time_var}) | {n_after_dedup:,} |
| 删除 Y/D 缺失 | 结果/处理变量 | {n_after_ymiss:,} |
| 最终样本 | — | {len(df):,} |
### 关键变量缺失率(清洗后)
| 变量 | 角色 | 缺失率 |
|------|------|--------|
{chr(10).join(f"| {v} | {role} | {df[v].isna().mean():.1%} |" for v, role in zip([Y_var, D_var, Z_var], ["Y", "D", "Z"]))}
### 识别策略专属变量
{chr(10).join(f"- `{v}`:已构建" for v in strategy_vars_built)}
### 数据质量问题记录
{quality_notes}
"""
os.makedirs("data/clean", exist_ok=True)
with open("data/clean/cleaning_log.md", "a", encoding="utf-8") as f:
f.write(log)
print("✅ 已写入 data/clean/cleaning_log.md")
```
---
## Common Pitfalls
| ❌ 常见错误 | ✅ 正确做法 |
|------------|------------|
| 合并前不检查合并键唯一性 | `assert df[key].is_unique` |
| 对 Y / D 自动 Winsorize | 缩尾前须有经济学判断 |
| 用全局均值填充 Y / D 缺失 | Y/D 应删除,不应插补 |
| DiD 用全局政策年份,忽视交错处理 | 用个体自身 `first_treated_year` |
| 覆盖原始数据 | `data/raw/` 仅读,`data/clean/` 用于保存 |
| 未记录样本量变化 | 每步都在 `cleaning_log.md` 记录 |
| 合并后不检查匹配率 | 低于 70% 需向用户报告 |
| 对 RDD 忘记密度检验 | McCrary 检验在 EDA 阶段执行 |
---
## Requirements
```bash
pip install pandas pyreadstat python-dotenv fredapi wbdata requests yfinance imf-reader
```
## Related Skills & Commands
- **`/data`**:调用本 skill 的上级命令
- **`results-analysis`**:接收本 skill 的输出,生成描述性统计与探索性分析
- **`did-analysis` / `rdd-analysis` / `iv-estimation` / `panel-data`**:应用本 skill 构建的变量
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.