Predicting Bankruptcy in the mining industry with Altman's Z-score and the Calcbench API

Edward Altman's Z-score is a formula for predicting corporate bankruptcy. Below we plot the change in industry aggregate Z-scores for mining companies. A Z-score above 2.99 is considered "safe", between 1.81 and 2.99 is a "gray" zone, below 1.81 is the "distress" zone. The troubles in the coal and metal mining industry are clearly illustrated.

Altman's Z-score @ Wikipedia

To replicate the below sign up for a Calcbench account, install the Calcbench Python API client from here and download this note book from github.

In [1]:
import calcbench as cb
import pandas as pd
pd.options.display.max_rows = 1000
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [2]:
z_score_metrics = ['CurrentAssets',
                   'CurrentLiabilities', 
                   'Assets', 
                   'RetainedEarnings', 
                   'EBIT', 
                   'MarketCapAtEndOfPeriod',
                   'Liabilities',
                   'Revenue']
In [3]:
SIC_codes = {
    "Oil And Gas Extraction" : 1300,
    "Metal Mining" : 1000,
    "Coal Mining" : 1200,
    "Mining Nonmetallic Minerals" : 1400,
}
In [8]:
def peer_group_z_score(peer_group):
    peer_group = peer_group[(peer_group.ticker != 'GMC') & (peer_group.ticker != 'PPI')] #GMC's marketvalue is off
    z_score_data = cb.normalized_dataframe(company_identifiers=list(peer_group.ticker), 
                                           metrics=z_score_metrics, 
                                           start_year=2008, start_period=0, 
                                           end_year=2017, end_period=0)
    aggregate_data = z_score_data.sum(level=[0], axis=1)
    return compute_z_score(aggregate_data), z_score_data
In [9]:
def compute_z_score(inputs):
    #from https://github.com/calcbench/notebooks/blob/master/z-score.ipynb
    working_capital = inputs['CurrentAssets'] - inputs['CurrentLiabilities']
    

    z_score = (1.2 * (working_capital / inputs['Assets']) + 
              1.4 * (inputs['RetainedEarnings'] / inputs['Assets']) +
              3.3 * (inputs['EBIT'] / inputs['Assets']) +
              0.6 * (inputs['MarketCapAtEndOfPeriod'] / inputs['Liabilities']) +
              .99 * (inputs['Revenue'] / inputs['Assets']))
    
    return z_score
In [10]:
peer_groups = [(industry, cb.companies(SIC_codes=[SIC_code])) for industry, SIC_code in SIC_codes.items()]
sp500 = cb.companies(index="SP500")
sp500_no_financials = sp500[sp500.sic_code & ((sp500.sic_code < 6000) | (sp500.sic_code >= 7000))] # There is a different z-score formulas for financials.
peer_groups.append(("SP500 (no financials)", sp500_no_financials))
industry_z_scores = [(industry, peer_group_z_score(peer_group)[0]) for industry, peer_group in peer_groups]
z_scores = pd.DataFrame.from_items(industry_z_scores)
c:\users\andrew kittredge\appdata\local\programs\python\python37-32\lib\site-packages\ipykernel_launcher.py:6: FutureWarning: from_items is deprecated. Please use DataFrame.from_dict(dict(items), ...) instead. DataFrame.from_dict(OrderedDict(items)) may be used to preserve the key order.
  
In [11]:
z_scores.plot(figsize=(18, 10))
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x96f030>
In [69]:
z_score, data = peer_group_z_score(peer_groups[0][1])
In [78]:
compute_z_score(data.swaplevel(0, 1, 1).ACI)
Out[78]:
period
2008         NaN
2009    1.496238
2010    2.372645
2011    0.872310
2012    0.393262
2013    0.211907
2014    0.247459
2015         NaN
Freq: A-DEC, dtype: float64