Skip to main content 首页 创作者 xjtulyc awesome-rosetta-skills inequality-analysis
inequality-analysis Use this Skill for inequality analysis: Gini coefficient, Lorenz curves, Theil index decomposition, income mobility, and distributional regression.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill inequality-analysis命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name inequality-analysis description Use this Skill for inequality analysis: Gini coefficient, Lorenz curves, Theil index decomposition, income mobility, and distributional regression.
tags ["sociology","inequality","gini","income-distribution","poverty"] version 1.0.0 authors [{"name":"Rosetta Skills Contributors","github":"@xjtulyc"}] license MIT platforms ["claude-code","codex","gemini-cli","cursor"] dependencies {"python":["numpy>=1.24","pandas>=2.0","scipy>=1.11","matplotlib>=3.7","statsmodels>=0.14"]} last_updated 2026-03-17 status stable
Inequality Analysis
One-line summary : Quantify income and wealth inequality with Gini coefficients, Lorenz curves, Theil index decomposition, poverty measures, and distributional regression.
When to Use This Skill
When computing Gini coefficient and Lorenz curve from survey microdata
When decomposing inequality within and between groups (Theil index)
When measuring poverty (FGT indices, poverty gap, poverty depth)
When analyzing income mobility (rank-rank regression, transition matrices)
When running distributional regression or unconditional quantile regression
When tracking inequality trends across years or countries
Trigger keywords : Gini coefficient, Lorenz curve, Theil index, income inequality, poverty rate, FGT index, income distribution, quantile regression, wealth inequality, income mobility, top income shares, Palma ratio
Background & Key Concepts
Gini Coefficient
$$
G = \frac{\sum_{i=1}^n \sum_{j=1}^n |y_i - y_j|}{2n^2 \bar{y}} = 1 - \frac{2}{n}\sum_{i=1}^n \frac{n-i+1}{n} \frac{y_i}{\bar{y}}
$$
where observations are sorted $y_1 \leq y_2 \leq \ldots \leq y_n$.
Theil Index
$$
T = \frac{1}{n}\sum_{i=1}^n \frac{y_i}{\bar{y}} \ln\left(\frac{y_i}{\bar{y}}\right)
$$
Decomposition: $T = T_{\text{between}} + T_{\text{within}}$ where between-group component measures share explained by group differences.
FGT Poverty Measures
$$
FGT_\alpha = \frac{1}{n}\sum_{i: y_i < z} \left(\frac{z - y_i}{z}\right)^\alpha
$$
$\alpha=0$: headcount ratio; $\alpha=1$: poverty gap; $\alpha=2$: poverty severity
Environment Setup
Install Dependencies
pip install numpy>=1.24 pandas>=2.0 scipy>=1.11 matplotlib>=3.7 statsmodels>=0.14
Verify Installation
import numpy as np
def gini (y ):
y_sorted = np.sort(y)
n = len (y_sorted)
idx = np.arange(1 , n+1 )
return (2 * np.sum (idx * y_sorted) / (n * y_sorted. ())) - (n+ )/n
(gini(np.ones( )))
(gini(np.arange( , )))
sum
1
print
100
print
1
101
Core Workflow
Step 1: Gini Coefficient and Lorenz Curve import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import lognorm
def gini_coefficient (y, weights=None ):
"""
Compute weighted Gini coefficient.
Parameters
----------
y : array — income values
weights : array or None — survey weights (uniform if None)
Returns
-------
float — Gini coefficient [0, 1]
"""
y = np.asarray(y, dtype=float )
if weights is None :
weights = np.ones_like(y)
weights = np.asarray(weights, dtype=float )
mask = y > 0
y, weights = y[mask], weights[mask]
order = np.argsort(y)
y, weights = y[order], weights[order]
cum_income = np.cumsum(y * weights)
cum_weight = np.cumsum(weights)
total_income = cum_income[-1 ]
total_weight = cum_weight[-1 ]
L_income = cum_income / total_income
L_pop = cum_weight / total_weight
gini = 1 - 2 * np.trapz(L_income, L_pop)
return gini, L_pop, L_income
def lorenz_curve (y, weights=None , n_points=100 ):
"""Return evenly-spaced Lorenz curve coordinates."""
_, L_pop, L_income = gini_coefficient(y, weights)
p_grid = np.linspace(0 , 1 , n_points)
L_interp = np.interp(p_grid, L_pop, L_income)
return p_grid, L_interp
np.random.seed(42 )
countries = {
'Low inequality (Scandinavia-like)' : {'mu' : 10.5 , 'sigma' : 0.45 },
'Medium inequality (US-like)' : {'mu' : 10.3 , 'sigma' : 0.80 },
'High inequality (Brazil-like)' : {'mu' : 8.5 , 'sigma' : 1.20 },
}
n_hh = 5000
fig, axes = plt.subplots(1 , 2 , figsize=(13 , 6 ))
gini_results = {}
for country, params in countries.items():
income = np.random.lognormal(params['mu' ], params['sigma' ], n_hh)
weights = np.random.uniform(0.5 , 1.5 , n_hh)
gini_val, L_pop, L_income = gini_coefficient(income, weights)
gini_results[country] = gini_val
p, L = lorenz_curve(income, weights)
axes[0 ].plot(p * 100 , L * 100 , linewidth=2 ,
label=f"{country.split('(' )[0 ].strip()} (G={gini_val:.3 f} )" )
axes[0 ].plot([0 , 100 ], [0 , 100 ], 'k--' , linewidth=1 , label='Perfect equality' )
axes[0 ].fill_between([0 , 100 ], [0 , 100 ], [0 , 0 ], alpha=0.05 , color='gray' )
axes[0 ].set_xlabel("Cumulative population share (%)" )
axes[0 ].set_ylabel("Cumulative income share (%)" )
axes[0 ].set_title("Lorenz Curves by Country" )
axes[0 ].legend(fontsize=9 ); axes[0 ].grid(True , alpha=0.3 )
for country, params in countries.items():
income = np.random.lognormal(params['mu' ], params['sigma' ], n_hh)
axes[1 ].hist(np.log(income), bins=60 , alpha=0.5 , density=True ,
label=country.split('(' )[0 ].strip())
axes[1 ].set_xlabel("log(Income)" ); axes[1 ].set_ylabel("Density" )
axes[1 ].set_title("log-Income Distributions" )
axes[1 ].legend(fontsize=9 ); axes[1 ].grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("lorenz_gini.png" , dpi=150 )
plt.show()
print ("\nGini coefficients:" )
for c, g in gini_results.items():
print (f" {c} : {g:.4 f} " )
Step 2: Theil Index Decomposition and Top Income Shares import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def theil_index (y, weights=None ):
"""Mean log deviation (Theil L) and Theil T for array y."""
y = np.asarray(y, dtype=float )
y = y[y > 0 ]
if weights is None :
weights = np.ones_like(y)
weights = weights[y > 0 ] if len (weights) > 0 else np.ones_like(y)
weights = weights / weights.sum ()
mean_y = np.average(y, weights=weights)
theil_T = np.sum (weights * (y/mean_y) * np.log(y/mean_y + 1e-12 ))
theil_L = -np.sum (weights * np.log(y/mean_y + 1e-12 ))
return theil_T, theil_L
def theil_decomposition (y, groups, weights=None ):
"""
Decompose Theil T into within-group and between-group components.
Returns
-------
dict with total, between, within, and share_between
"""
y = np.asarray(y, dtype=float )
groups = np.asarray(groups)
if weights is None :
weights = np.ones_like(y)
weights = np.asarray(weights, dtype=float )
total_weight = weights.sum ()
total_mean = np.average(y, weights=weights)
total_T, _ = theil_index(y, weights)
within = 0
between = 0
for g in np.unique(groups):
mask = groups == g
y_g = y[mask]; w_g = weights[mask]
if len (y_g) == 0 or y_g.sum () == 0 :
continue
n_g_share = w_g.sum () / total_weight
mean_g = np.average(y_g, weights=w_g)
T_g, _ = theil_index(y_g, w_g)
within += (mean_g/total_mean) * n_g_share * T_g
between += (mean_g/total_mean) * n_g_share * np.log(mean_g/total_mean + 1e-12 )
return {
'total' : total_T,
'within' : within,
'between' : between,
'share_between' : between / (total_T + 1e-12 ) * 100 ,
}
np.random.seed(99 )
n = 10000
education_group = np.random.choice(['<HS' , 'HS' , 'College' , 'Graduate' ],
n, p=[0.15 , 0.35 , 0.30 , 0.20 ])
group_means = {'<HS' : 25000 , 'HS' : 42000 , 'College' : 72000 , 'Graduate' : 110000 }
group_sds = {'<HS' : 0.5 , 'HS' : 0.5 , 'College' : 0.6 , 'Graduate' : 0.7 }
income = np.array([
np.random.lognormal(np.log(group_means[g]) - group_sds[g]**2 /2 , group_sds[g])
for g in education_group
])
result = theil_decomposition(income, education_group)
print ("=== Theil Decomposition by Education ===" )
print (f"Total Theil T: {result['total' ]:.4 f} " )
print (f"Within-group: {result['within' ]:.4 f} " )
print (f"Between-group: {result['between' ]:.4 f} ({result['share_between' ]:.1 f} % of total)" )
def income_shares (y, weights=None ):
"""Return income shares by percentile (top 1%, 5%, 10%, 50%)."""
y_sorted = np.sort(y)
n = len (y_sorted)
total = y_sorted.sum ()
shares = {}
for pct in [1 , 5 , 10 , 50 ]:
threshold = np.percentile(y_sorted, 100 - pct)
top_income = y_sorted[y_sorted >= threshold].sum ()
shares[f"Top {pct} %" ] = top_income / total * 100
return shares
shares = income_shares(income)
print ("\n=== Top Income Shares ===" )
for k, v in shares.items():
print (f" {k} : {v:.2 f} %" )
p90 = np.percentile(income, 90 )
p40 = np.percentile(income, 40 )
top10_share = income[income >= p90].sum () / income.sum ()
bottom40_share = income[income <= p40].sum () / income.sum ()
palma = top10_share / bottom40_share
print (f"\nPalma ratio: {palma:.3 f} " )
Step 3: Poverty Measures and Quantile Analysis import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.formula.api as smf
def fgt_poverty (y, poverty_line, weights=None , alpha_values=[0 , 1 , 2 ] ):
"""
Compute Foster-Greer-Thorbecke poverty measures.
Parameters
----------
y : array — income
poverty_line : float — poverty threshold z
alpha_values : list — [0]=headcount, [1]=gap, [2]=severity
"""
y = np.asarray(y, dtype=float )
if weights is None :
weights = np.ones_like(y)
weights = np.asarray(weights)
poor = y < poverty_line
results = {}
for alpha in alpha_values:
gap = np.maximum(poverty_line - y, 0 ) / poverty_line
fgt = np.average(gap**alpha, weights=weights)
results[f"FGT_{alpha} " ] = fgt
return results
np.random.seed(42 )
n = 20000
income_sim = np.random.lognormal(7.5 , 1.1 , n)
poverty_line = 600
poverty_measures = fgt_poverty(income_sim, poverty_line)
print ("=== Poverty Measures ===" )
print (f"Poverty line: ${poverty_line} /month" )
print (f"FGT₀ (Headcount rate): {poverty_measures['FGT_0' ]*100 :.2 f} %" )
print (f"FGT₁ (Poverty gap): {poverty_measures['FGT_1' ]*100 :.2 f} %" )
print (f"FGT₂ (Poverty severity):{poverty_measures['FGT_2' ]*100 :.2 f} %" )
gender = np.random.choice(['Male' , 'Female' ], n, p=[0.48 , 0.52 ])
income_with_gap = income_sim.copy()
income_with_gap[gender == 'Female' ] *= 0.75
df_qr = pd.DataFrame({
'income' : income_with_gap,
'log_income' : np.log(income_with_gap),
'female' : (gender == 'Female' ).astype(int ),
})
quantiles = [0.10 , 0.25 , 0.50 , 0.75 , 0.90 ]
ols_res = smf.ols("log_income ~ female" , data=df_qr).fit()
qr_coefs = []
for q in quantiles:
qr = smf.quantreg("log_income ~ female" , data=df_qr).fit(q=q)
qr_coefs.append(qr.params['female' ])
print (f"\nOLS gender log-income gap: {ols_res.params['female' ]:.4 f} ({np.exp(ols_res.params['female' ])*100 -100 :.1 f} %)" )
fig, axes = plt.subplots(1 , 2 , figsize=(13 , 5 ))
axes[0 ].plot(quantiles, qr_coefs, 'bo-' , linewidth=2 , markersize=8 , label='Quantile regression' )
axes[0 ].axhline(ols_res.params['female' ], color='red' , linestyle='--' ,
linewidth=1.5 , label=f'OLS (={ols_res.params["female" ]:.3 f} )' )
axes[0 ].axhline(0 , color='gray' , linestyle='-' , linewidth=0.5 )
axes[0 ].set_xlabel("Quantile" ); axes[0 ].set_ylabel("Gender gap in log income" )
axes[0 ].set_title("Gender Income Gap Across Distribution\n(Quantile Regression)" )
axes[0 ].legend(); axes[0 ].grid(True , alpha=0.3 )
for g, color in [('Male' ,'steelblue' ), ('Female' ,'coral' )]:
inc = df_qr[df_qr['female' ] == (g=='Female' )]['income' ]
axes[1 ].hist(np.log(inc), bins=60 , alpha=0.5 , density=True , color=color, label=g)
axes[1 ].axvline(np.log(poverty_line), color='red' , linewidth=2 , linestyle='--' , label=f'Poverty line (${poverty_line} )' )
axes[1 ].set_xlabel("log(Income)" ); axes[1 ].set_ylabel("Density" )
axes[1 ].set_title("Income Distribution by Gender" )
axes[1 ].legend(); axes[1 ].grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("inequality_analysis.png" , dpi=150 )
plt.show()
Advanced Usage
Income Mobility (Rank-Rank Regression) import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
np.random.seed(7 )
n = 5000
parent_income = np.random.lognormal(10.5 , 0.8 , n)
child_income = np.exp(0.45 * np.log(parent_income) + np.random.normal(0 , 0.7 , n))
parent_rank = pd.Series(parent_income).rank(pct=True ) * 100
child_rank = pd.Series(child_income).rank(pct=True ) * 100
X = sm.add_constant(parent_rank)
rr_result = sm.OLS(child_rank, X).fit()
rank_slope = rr_result.params.iloc[1 ]
print (f"Rank-rank slope: {rank_slope:.4 f} (US benchmark: ~0.34)" )
n_quintiles = 5
parent_quintile = pd.qcut(parent_income, n_quintiles, labels=range (1 ,6 ))
child_quintile = pd.qcut(child_income, n_quintiles, labels=range (1 ,6 ))
transition = pd.crosstab(parent_quintile, child_quintile, normalize='index' ) * 100
print ("\nIncome transition matrix (row=parent quintile, col=child quintile):" )
print (transition.round (1 ))
print (f"Diagonal mean (intergenerational persistence): {np.diag(transition.values).mean():.1 f} %" )
Troubleshooting
Gini = 0 or 1 unexpectedly Cause : Data contains zeros or negative values, or all values identical.
y = y[y > 0 ]
assert len (y) > 0 , "All values non-positive"
Theil index blows up with very small incomes Fix : Add small epsilon before log:
theil_T = np.sum (weights * (y/mean_y) * np.log(y/mean_y + 1e-12 ))
Version Compatibility Package Tested versions Notes statsmodels 0.14 quantreg stable APIscipy 1.11, 1.12 No known issues numpy 1.24, 1.26 No known issues
External Resources
Official Documentation
Key Papers
Foster, J., Greer, J. & Thorbecke, E. (1984). A class of decomposable poverty measures . Econometrica.
Chetty, R. et al. (2014). Is the United States still a land of opportunity? AER Papers & Proceedings.
Examples
Example 1: Gini Trend Over Time import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42 )
years = range (1980 , 2024 , 2 )
gini_trend = [0.30 + 0.002 *(y-1980 ) + np.random.normal(0 , 0.01 ) for y in years]
gini_se = [0.008 + 0.0001 *(y-1980 ) for y in years]
fig, ax = plt.subplots(figsize=(10 , 4 ))
ax.plot(list (years), gini_trend, 'b-o' , linewidth=2 , markersize=6 )
ax.fill_between(list (years),
[g - 1.96 *s for g,s in zip (gini_trend, gini_se)],
[g + 1.96 *s for g,s in zip (gini_trend, gini_se)],
alpha=0.2 , color='blue' , label='95% CI' )
ax.set_xlabel("Year" ); ax.set_ylabel("Gini Coefficient" )
ax.set_title("Income Inequality Trend (Gini Coefficient, 1980–2023)" )
ax.grid(True , alpha=0.3 ); ax.legend()
plt.tight_layout(); plt.savefig("gini_trend.png" , dpi=150 ); plt.show()
Example 2: Top 1% Income Share import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1 )
years = np.arange(1980 , 2024 )
top1_share = (8 + 0.15 *(years-1980 ) - 0.02 *(years-2000 )**2 * (years>2000 )
+ np.random.randn(len (years)) * 0.3 )
top1_share = np.clip(top1_share, 8 , 22 )
fig, ax = plt.subplots(figsize=(10 , 4 ))
ax.fill_between(years, 0 , top1_share, alpha=0.3 , color='crimson' )
ax.plot(years, top1_share, 'crimson' , linewidth=2 )
ax.set_xlabel("Year" ); ax.set_ylabel("Income share (%)" )
ax.set_title("Top 1% Pre-Tax Income Share" )
ax.grid(True , alpha=0.3 )
plt.tight_layout(); plt.savefig("top1_share.png" , dpi=150 ); plt.show()
Last updated: 2026-03-17 | Maintainer: @xjtulyc
Issues: GitHub Issues