Age of Discord Math
Elites
According to [1] elite numbers, $E$ can change according to
$$ \dot{E} = rE + \mu N \qquad \qquad (1) $$
where $N$ is the general population, $r$ is rate of population growth, and $\mu$ is the balance of upward and downward social mobility. We can simplify by focusing on relative elite numbers $e = E/N$, we can subtitute $E = e N$, before that that derivative
$$ \dot{E} = \dot{e} N + e \dot{N} $$
Plug into (1) and since population growth is also $r$, meaning $\dot{N} = r N$
$$ \dot{e} N + e \dot{N} = r e N + \mu N $$
$$ \dot{e} N = r e N + \mu N - e r N $$
$$ \dot{e} = \mu \qquad \qquad (2) $$
The parts above are the logical result of natural growth of population and elites being the same rate $r$.
Looking at $\mu$, Turchin assumes the upward and downward social mobility, $\mu$ inversely related to relative wage. Regular people earning less means elites earning more.
$$ \mu = \mu_0 \left( \frac{w_0}{w} - 1 \right) $$
where $μ_0$ and $w_0$ are scaling parameters. Therefore (2) becomes
$$ \dot{e} = \mu_0 \frac{w_0 - w}{w} $$
Computing $w,e,\epsilon$ for US based on 1930-2010 data (below reproduces the graph 13.1 from AOD),
import pandas as pd
data = pd.read_csv('aod_psi.csv',index_col=0)
w_0 = 1
mu_0 = 0.1
lam = 0.5
relWage1 = data['ProdWage'] / data['GDPpc']
relWage1 = relWage1/relWage1.loc[1980]
relWage2 = data['UnskillWage'] / data['GDPpc']
relWage2 = relWage2/relWage2.loc[1980]
data['RelWage'] = (relWage1 + relWage2) / 2
cols = ['RelWage','RelDebt','Distrust']
data[cols] = data[cols].interpolate()
data = data[data.index > 1944]
data.loc[1945]['elite'] = 1
for t in range(1946,2021):
data.loc[t]['elite'] = data.loc[t-1]['elite'] + mu_0*( w_0 - data.loc[t-1]['RelWage'] ) / \
data.loc[t-1]['RelWage']
data['epsilon'] = (1 - lam*data['RelWage'])/data['elite']
data['epsilon'] = data['epsilon']/data.iloc[0]['epsilon']
data['Urbanization'] = data['Urbanization']/100.
data['Age20_29'] = data['Age20_29']/60.
data['RelDebt'] = data['RelDebt']/100.
data['Distrust'] = data['Distrust']/100.
res = data[['RelWage','elite','epsilon']]
res.columns = ['w','e','epsilon']
res.plot()
plt.savefig('psi1.jpg')
Political Stress Indicator
PSI, or $\Psi$, is shown as [1, pg. 237]
$$ \Psi = w^{-1} \frac{N_{urb}}{N} A_{20-29} \frac{\epsilon^{-1}e}{s} \frac{Y}{N} (1-T) $$
$N_{urb}/N$ is the urbanization rate, $A_{20-29}$ is the youth bulge index, $\epsilon$ is relative elite income computed previously. $Y/N$ is national debt scaled by GDP. Proportionality constant $s$ is the number of government employees per total population. Trust in government institutions is $T$, distrust is computed via $1-T$.
Again using US data,
data['PSI'] = 100*(1/data['RelWage'])*data['Urbanization']*data['Age20_29']*(data['elite']/data['epsilon'])*data['RelDebt']*data['Distrust']
data['PSI'].plot()
plt.savefig('psi2.jpg')
Turbulent times are ahead.
Predicting Wages
In Age of Discord Turchin shows an approach to predict wages, the formula is
$$ W_{t+\tau} = \alpha \left( \frac{G_t}{N_t} \right)^\alpha \left( \frac{D_t}{S_t} \right)^\beta C_t^\gamma $$
Where $W$ is the real wage, $G/N$ is the real GDP per capita, $D/S$ is the balance of labor demand and supply, and $C$ stands for the effect of non-market forces (culture).
Take log products become sums
$$ \log W_{t+\tau} = A + \alpha \log \left( \frac{G_t}{N_t} \right) + \beta \log \left( \frac{D_t}{S_t} \right) + \gamma \log C_t + \epsilon_t $$
The formula above is now regression-able.
Using frequency of certain words to gauge "the culture" of an era, below is the word "greed" via Google Books.
import impl as u, pandas as pd
df = pd.read_csv('gr-ngram.csv',header=None)
df['DATE'] = pd.to_datetime(["%d-01-01" % x for x in list(range(1800,2020))])
keyw = df.set_index('DATE')[0]
keyw.plot(title="Count for the Word 'Greed' in Published Work")
We put it all together below
# gdpcap wage prod lab_force minwage
df = u.get_fred(1970,['A939RC0A052NBEA','LES1252881600Q','OPHNFB','CLF16OV','FEDMINNFRWG'])
df = df.interpolate()
df3 = df.copy()
idx = list(range(1800,2020))
idx = ["%d-01-01" % x for x in idx]
keyw.columns = ['greed']
df3['greed'] = keyw
df3 = df3.interpolate()
df3.columns = ['G','W','Prod','S','M','greed']
df3['C'] = df3['greed'].rdiv(1)
df3['D'] = df3.G / df3.Prod
df3 = df3.resample('A').first()
df3['W'] = df3.W.shift(1)
import statsmodels.formula.api as smf
results = smf.ols('np.log(W) ~ np.log(M) + np.log(G) + np.log(D/S) + np.log(C)', data=df3).fit()
print (results.rsquared)
0.8857767077052572
df3['Wpred'] = df3.apply(lambda x: results.params['Intercept'] +
np.log(x['M'])*results.params['np.log(M)'] +
np.log(x['G'])*results.params['np.log(G)'] +
np.log(x['D']/x['S'])*results.params['np.log(D / S)'] +
np.log(x['C'])*results.params['np.log(C)'],axis=1)
df3['Wpred'] = np.exp(df3['Wpred'])
pred = df3[df3.index > '1980-01-01']
pred[['W','Wpred']].plot()
You can follow microblog postings on this and similar subjects on the main page of this site.
References
[1] Turchin, Age of Discord