Modeling Incomes and Inequalities

[This article was first published on Freakonometrics » R-english, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Last week, in our Inequality course, we’ve been looking at data. We started with some simulated data, only a few of them

> library("ineq")
> load(url("http://freakonometrics.free.fr/income_5.RData"))
> (income=sort(income))
[1]  19233  23707  53297  61667 218662

How could we say that there is inequality in this sample? If we look at the wealth owned by the poorest, the poorest person (1 out of 5) owns 5% of the wealth; the bottom two (2 out of 5) own 11%, etc

> income[1]/sum(income)
[1] 0.05107471
> sum(income[1:2])/sum(income)
[1] 0.1140305
> sum(income[1:3])/sum(income)
[1] 0.2555648
> sum(income[1:4])/sum(income)
[1] 0.4193262

If we plot those values, we get Lorenz curve

> plot(Lc(income))
> points(c(0:5)/5,c(0,cumsum(income)/sum(income)),pch=19,col="blue")

Now, what if we got 500 observations (and not only 5). In that case, a natural tool to visualize those data (or to be  more specific, their distribution) is the histogram

> load(url("http://freakonometrics.free.fr/income_500.RData"))
> summary(income)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2191   23830   42750   77010   87430 2003000 
> hist(log(income),probability=TRUE,col="light blue",border="white")
> lines(density(log(income)),col="red")
> u=seq(6,15,length=251)
lines(u,dnorm(u,mean(log(income)),sd(log(income))),col="blue")

Here we use an histogram to visualize our sample. But not on the income, on the logarithm of the income (because of some outliers, we cannot visualize anything on the histogram). Now, it is possible to compute Gini index to get some information about inequalities

> gini=function(x){
+ n=length(x)
+ mu=mean(x)
+ g=2/(n*(n-1)*mu)*sum((1:n)*sort(x))-(n+1)/(n-1)
+ return(g)}

The problem is that, in practice, having an index without any confidence interval can be meaningless. To compute a confidence interval, we can use a bootstrap procedure,

> boot=function(x,f,b=500){
+ n=length(x)
+ F=rep(NA,n)
+ for(s in 1:b){
+ idx=sample(1:n,size=n,replace=TRUE)
+ F[s]=f(x[idx])}
+ return(F)}
> G=boot(income,gini,1000)
> hist(G,col="light blue",border="white",probability=TRUE)

The red segments is the 90% confidence interval,

> quantile(G,c(.05,.95))
       5%       95% 
0.4954235 0.5743917 

We did include also a blue line with a Gaussian distribution,

> segments(quantile(G,.05),1,quantile(G,.95),1,
+ col="red",lwd=2)
> u=seq(.4,.65,length=251)
> lines(u,dnorm(u,mean(G),sd(G)),
+ col="blue")

Another popular tool is the Pareto plot, where we plot the logarithm of the cumulative survival function against the logarithm of the income,

> n=length(income)
> x=log(sort(income))
> y=log((n:1)/n)
> plot(x,y)  

If points were on a straight line, it would mean that incomes can be modeled with a Pareto distribution. Obviously, it is not the case here.

We’ve seen previously how to get Lorenz curve. Actually, it is possible to get also Lorenz curve for some parametric distribution, e.g. some log-normal ones,

> plot(Lc(income))
> lines(Lc.lognorm,param=1.5,col="red")
> lines(Lc.lognorm,param=1.2,col="red")
> lines(Lc.lognorm,param=.8,col="red")

Here, it sounds reasonnable to claim that a log-normal distribution would be a good fit. But maybe not a Pareto distribution

> plot(Lc(income))
> lines(Lc.pareto,param=2,col="red")
> lines(Lc.pareto,param=1.5,col="red")
> lines(Lc.pareto,param=1.2,col="red")

Actually, it is possible to fit some parametric distributions. Observe that sometimes, we have to change the scale, and use ‘000s of dollars, instead of dollars,

> library(MASS)
> fitdistr(income/1e3,"gamma")
      shape           rate    
  1.0812757769   0.0140404379 
 (0.0604530180) (0.0009868055)

Now, consider two distributions, a Gamma one, and a log-normal one (fitted with maximum likelihood techniques)

> (fit_g <- fitdistr(income/1e2,"gamma"))
      shape           rate    
  1.0812757769   0.0014040438 
 (0.0473722529) (0.0000544185)
> (fit_ln <- fitdistr(income/1e2,"lognormal"))
    meanlog       sdlog   
  6.11747519   1.01091329 
 (0.04520942) (0.03196789)

We can visualize the densities

> u=seq(0,2e5,length=251)
> hist(income,breaks=seq(0,2005000,by=5000),
+ col=rgb(0,0,1,.5),border="white",
+ xlim=c(0,2e5),probability=TRUE)
> v_g <- dgamma(u/1e2, fit_g$estimate[1],
+ fit_g$estimate[2])/1e2
> v_ln <- dlnorm(u/1e2, fit_ln$estimate[1],
+ fit_ln$estimate[2])/1e2
> lines(u,v_g,col="red",lwd=2)
> lines(u,v_ln,col=rgb(1,0,0,.4),lwd=2)

Here, it looks like the log-normal is a good candidate. We can also plot the cumulative distribution functions

> x <- sort(income)
> y <- (1:500)/500
> plot(x,y,type="s",col="black",xlim=c(0,250000))
> v_g <- pgamma(u/1e2, fit_g$estimate[1],
+ fit_g$estimate[2])
> v_ln <- plnorm(u/1e2, fit_ln$estimate[1],
+ fit_ln$estimate[2])
> lines(u,v_g,col="red",lwd=2)
> lines(u,v_ln,col=rgb(1,0,0,.4),lwd=2)

Now, consider some more realistic situation, where we do not have samples from surveys, but binned data,

> load(url("http://freakonometrics.free.fr/income_binned.RData"))
> head(income_binned)
    low  high number  mean std_err
1     0  4999     95  3606     964
2  5000  9999    267  7686    1439
3 10000 14999    373 12505    1471
4 15000 19999    350 17408    1368
5 20000 24999    329 22558    1428
6 25000 29999    337 27584    1520
> tail(income_binned)
      low   high number   mean std_err
46 225000 229999     10 228374    1197
47 230000 234999     13 232920    1370
48 235000 239999     11 236341    1157
49 240000 244999     14 242359    1474
50 245000 249999     11 247782    1487
51 250000    Inf    228 395459  189032

There is a new package to model that kind of data,

> library(binequality)
> n <- nrow(income_binned)
> fit_LN <- fitFunc(ID=rep("Fake Data",n), 
+ hb=income_binned[,"number"], 
+ bin_min=income_binned[,"low"], 
+ bin_max=income_binned[,"high"],
+ obs_mean=income_binned[,"mean"], 
+ ID_name="Country", distribution=LNO, 
+ distName="LNO")
Time difference of 2.101471 secs
for LNO fit across 1 distributions 

Here, we can fit a log-normal distribution (see Methods for estimating inequality from binned incomes for more details about the methodology)

> N=income_binned$number
> y2=N/sum(N)/diff(income_binned$low) 
> u=seq(min(income_binned$low),
+  max(income_binned$low),length=101)
> v=dlnorm(u,fit_LN$parameters[1],
+ fit_LN$parameters[2])
> plot(u,v,col="blue",type="l",lwd=2)
> for(i in 1:(n-1)) rect(income_binned$low[i],0,
+ income_binned$high[i],y2[i],col=rgb(1,0,0,.2),
+ border="white")

Here, on the histogram (since we have binned data, it is natural to plot an histogram), we can see that the fitted log-normal distribution is great. Actually, to be honest, data were simulated from a log-normal distribution, so it makes sense....

> N <- income_binned$number
> y1 <- cumsum(N)/sum(N) 
> u <- seq(min(income_binned$low),
+ max(income_binned$low),length=101)
> v <- plnorm(u,fit_LN$parameters[1],
+ fit_LN$parameters[2])
> plot(u,v,col="blue",type="l",lwd=2)
> for(i in 1:(n-1)) rect(income_binned$low[i],0,
+ income_binned$high[i], y1[i],col=rgb(1,0,0,.2))
> for(i in 1:(n-1)) rect(income_binned$low[i],
+ y1[i],income_binned$high[i],c(0,y1)[i],
+ col=rgb(1,0,0,.4))

For the cumulated distribution function, I consider either the worst case scenario (everyone in a bin get the lower possible income) and the best case (everyone has the highest possible income).

It is also possible to fit all standard GB2 distributions

> fits=run_GB_family(ID=rep("Fake Data",n),
+ hb=income_binned[,"number"],
+ bin_min=income_binned[,"low"],
+ bin_max=income_binned[,"high"],
+ obs_mean=income_binned[,"mean"],
+ ID_name="Country")

To get the best candidate, look at

> fits$fit.filter[,c("gini","aic","bic")]

Now, that was fine, but those were simulated data. What about real data now? Here we go

> data = read.table(
+ "http://freakonometrics.free.fr/us_income.txt",
+ sep=",",header=TRUE)
> head(data)
    low  high number_1000s  mean std_err
1     0  4999         4245  1249      50
2  5000  9999         5128  7923      30
3 10000 14999         7149 12389      28
4 15000 19999         7370 17278      26
5 20000 24999         7037 22162      27
6 25000 29999         6825 27185      28
> n <- nrow(data)
> fit_LN <- fitFunc(ID=rep("US",n), 
+ hb=data[,"number_1000s"], 
+ bin_min=data[,"low"],
+ bin_max=data[,"high"], 
+ obs_mean=data[,"mean"], 
+ ID_name="Country", 
+ distribution=LNO, distName="LNO")
Time difference of 0.1855791 secs
for LNO fit across 1 distributions

Again, I try to fit a log-normal distribution

> N=data$number
> y2=N/sum(N)/diff(data$low) 
> u=seq(min(income_binned$low),
+ max(income_binned$low),length=101)
> v=dlnorm(u,fit_LN$parameters[1],
+ fit_LN$parameters[2])
> plot(u,v,col="blue",type="l",lwd=2)
> for(i in 1:(n-1)) rect(data$low[i],
+ 0,data$high[i],y2[i],col=rgb(1,0,0,.2))

But here, the fit is rather poor. Again, we can estimate all standard GB2 distribution

> 
> fits=run_GB_family(ID=rep("US",n), 
+ hb=data[,"number_1000s"], 
+ bin_min=data[,"low"],
+ bin_max=data[,"high"],
+ obs_mean=data[,"mean"],
+ ID_name="Country")

We can get Gini index, AIC and BIC

> fits$fit.filter[,c("gini","aic","bic")]
       gini      aic      bic
1  4.413431 825368.5 825407.3
2  4.395080 825598.8 825627.9
3  4.451881 825495.7 825524.8
4  4.480850 825881.7 825910.8
5  4.417276 825323.6 825352.7
6  4.922122 832408.2 832427.6
7  4.341036 827065.2 827084.6
8  4.318667 826112.8 826132.2
9        NA 831054.2 831073.6
10       NA       NA       NA

to see that the best distribution seems to be a Generalized Gamma.

To leave a comment for the author, please follow the link and comment on their blog: Freakonometrics » R-english.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)