Skip to main content سوق المهارات اكتشف واستكشف مهارات الذكاء الاصطناعي التي بناها المجتمع.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
نسخ Promptعرض تفاصيل Prompt يتجاوز الأمر المباشر Prompt المخصّص للمراجعة. افحص المصدر قبل تشغيله.
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill inequality-analysisيبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
تحميل Zip جاري التحميل... المهن ذات الصلة SOC
استنادا إلى تصنيف SOC المهني
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