Four major chronic diseases and premature mortality | by Daniel Wu | Towards Data Science

In part 2 of our CDC Chronic Disease indicator dataset, our analysis revealed several areas with highly correlated interrelationships — indicators within the cardiovascular disease, chronic kidney disease, diabetes, and select indicators in the overarching conditions “social determinants” category. While there are also highly correlated relationships in other areas such as cancer and COPD, we’ll be focusing primarily on the former set in this final blog post.
While Figure 1 from the previous post looked at the relationships among all the indicators, Table 5 from the previous post showed there were a number of top correlation pairs. By looking at the recurring patterns of indicators by specific topic, we can narrow down the scope of the topics of interest. The same code was used previously was used to build the correlation heatmap in the below six figures (red — higher correlation):
Below we go through each of the correlation heatmaps pair by pair. At the end of section 6, there will be a reference to the actual Questions and QuestionID (QIDs). Feel free to skip if that helps understand the top specific indicator questions.
Summary of Detailed Findings:
As we dive into each highly correlated population health chronic indicators by topic and now by individual Question ID pairs, representing the individual indicators, the theme of interrelated chronic illnesses are reinforced. The pattern of comorbidities among diabetes, cardiovascular disease, and chronic kidney disease shows up in general indicators and some more specific indicators. For example, general indicators are broad indicators that appear to capture overall conditions such as DIA1_2 “Mortality due to diabetes reported as any listed cause of death.” These high level indicators with high positive correlation are bound to more discrete mortality outcomes as well, which while obvious, poses questionable ability to bring actionable next steps for individuals. Rather, it implies that the practical route presses towards prevention as a key approach in population health.
Interestingly, various overarching conditions (OVC) indicators appeared over and over such as OVC5_0 “Premature mortality among adults aged 45–64 years.” We see OVC5_0 paired with all of the top 5 cardiovascular disease indicators, top 3 chronic kidney disease indicators, and the top 4 diabetes indicators. Such relationship tends to speak to the comorbidities of these three interrelated issues and premature mortality for adults 45–64 years of age.
See below for the findings of the pairs, or jump two sections below as we shift gears to an analysis on the stratifications by gender and race, and the corresponding visualizations.
1. Chronic Kidney Disease and Cardiovascular Disease (CKD x CVD)
top_corr_pair[(top_corr_pair['Topic1'] == ('CKD')) & (top_corr_pair['Topic2'] == ('CVD'))].head()
Chronic kidney disease indicator CKD1_0 is for “Mortality with end-stage renal disease.” On the cardiovascular disease side, CVD1_1 to CVD1_5 are “Mortality from total cardiovascular disease,” “Mortality from diseases of the heart,” “Mortality from coronary heart disease,” “Mortality from heart failure,” and “Mortality from cerebrovascular disease (stroke),” respectively.
2. Chronic Kidney Disease and Diabetes (CKD x DIA)
Chronic kidney disease indicator CKD2_1 and CKD2_2 are “Incidence of treated end-stage renal disease” and “Incidence of treated end-stage renal disease attributed to diabetes,” respectively. On the diabetes side, DIA1_1 and DIA1_2 are “Mortality due to diabetes reported as any listed cause of death” and “Mortality with diabetes ketoacidosis reported as any listed cause of death,” respectively. DIA9_0 is “Hospitalization with diabetes as a listed diagnosis.” DIA4_0 is “Amputation of a lower extremity attributable to diabetes.”
3. Chronic Kidney Disease and Overarching Conditions (CKD x OVC)
Overarching Condition indicators OVC5_0 and OVC6_1 are “Premature mortality among adults aged 45–64 years” and “Fair or poor self-rated health status among adults aged ≥ 18 years,” respectively. These indicators are highly correlated with the same Diabetes indicators DIA1_1, DIA1_2, DIA4_0, and DIA9_0. Additionally we’re seeing relatively high correlation between DIA2_1 “High school completion among adults aged 18–24 years” and OVC6_1 with those with fair or poor self-rated health.
4. Cardiovascular Disease and Diabetes (CVD x DIA)
When comparing Cardiovascular and Diabetes indicators, the top correlated indicators start with DIA1_1, which is a general QuestionID of any mortality from diabetes, and also include DIA9_0 hospitalization with diabetes in the diagnosis. The related cardiovascular indicators include CVD1_4 (heart failure), CVD3_1 (hospitalization), CVD1_5 (mortality from stroke), CVD1_1 (general mortality from cardiovascular), and CVD1_2 (mortality from diseases of the heart).
5. Cardiovascular Disease and Overarching Conditions (CVD x OVC)
When comparing highly correlated Cardiovascular Disease and Overarching Conditions indicator OVC5_0 “premature mortality 45–64 years adults,” the top correlating indicators are belong to the group of CVD1_X Question IDs (QID1), ranging from heart disease to stroke.
6. Diabetes and Overarching Conditions (DIA x OVC)
Overarching Condition indicators OVC5_0 and OVC6_1 are “Premature mortality among adults aged 45–64 years” and “Fair or poor self-rated health status among adults aged ≥ 18 years,” respectively. These indicators are highly correlated with the same Diabetes indicators DIA1_1, DIA1_2, DIA4_0, and DIA9_0. Additionally we’re seeing relatively high correlation between DIA2_1 “High school completion among adults aged 18–24 years” and OVC6_1 with those with fair or poor self-rated health.
Reference for QuestionID Indicator Codes
Since Figures/Tables 1 to 6 visualized or listed the relationships, it would be helpful to take a look again at the respective questions for the Question IDs (QID1/QID2) to better understand the specific indicators. Using groupby() method on the df_new dataframe and then .loc[] to pull out the respective topics, I’m able to view a list of the QuestionID and Questions.
df_new_QTQLY = df_new[['QuestionID','Topic','Question','LocationAbbr','YearStart']].groupby(['Topic','QuestionID','Question']).count()df_new_QTQLY.loc['Cardiovascular Disease']
Stratification Analysis on Adult Premature Mortality — Gender
Since OVC5_0 indicator “Premature mortality of adults 45–64 years of age” appear repeatedly, let’s look into what this indicator can tell us by stratification of gender and race. We’re going to create df_new_OVC5_0_gender dataframe based on df_new based on the following parameters:
df_new_OVC5_0_gender = df_new[ (df_new['QuestionID'] == 'OVC5_0') & (df_new['StratificationCategory1'] == 'Gender') & (df_new['DataValueUnit'] == 'cases per 100,000')]df_new_OVC5_0_gender.info()
In the above dataframe, df_new[‘QuestionID’] == ‘OVC5_0’ takes the subset of data related to our specific indicator. However, this includes stratifications for both race and gender. To look at the gender stratification, df_new[‘StratificationCategory1’] == ‘Gender’ is included. The remaining dataframe shows in DataValueType to include either a ‘Number’ or an ‘Age adjusted rate.’ The ‘Number’ represents a crude rate, which is the “total burden of a health outcome to a community.” The ‘age adjusted rate’ is used to make more fair comparisons among groups whether it’s age, gender, or race. (More detailed explain here). It appears that when DataValueType = ‘Age adjusted rate,’ DataValueUnit = ‘cases per 100,000’ so I’ve included df_new[‘DataValueUnit’] == ‘cases per 100,000.’ The count shows that the OVC topic and StratificationCategory1 is not as populated as the full dataset which is expected:
<class 'pandas.core.frame.DataFrame'>Int64Index: 1020 entries, 268047 to 403039Data columns (total 16 columns):YearStart                    1020 non-null int64LocationAbbr                 1020 non-null objectTopic                        1020 non-null objectQuestion                     1020 non-null objectDataValueUnit                1020 non-null objectDataValueType                1020 non-null objectDataValueAlt                 1020 non-null float64StratificationCategory1      1020 non-null objectStratification1              1020 non-null objectLocationID                   1020 non-null int64TopicID                      1020 non-null objectQuestionID                   1020 non-null objectDataValueTypeID              1020 non-null objectStratificationCategoryID1    1020 non-null objectStratificationID1            1020 non-null objectQuestionAbbr                 1020 non-null objectdtypes: float64(1), int64(2), object(13)df_new_OVC5_0_gender.head()
Using the groupby summary, we see that the data available runs for 5 years as opposed to the entire 15 years of the overall dataset. The following will also plot out the trends and in a seaborn barplot.
df_new_OVC5_0_gender1 = df_new_OVC5_0_gender.groupby(['Stratification1','YearStart'])df_new_OVC5_0_gender1.mean().drop('LocationID',axis=1).round()plt.figure(figsize=(16, 6))sns.barplot(x='YearStart',y='DataValueAlt',data=df_new_OVC5_0_gender,hue='Stratification1',ci=None,saturation=0.7)
Over the course of 2010 to 2014, the data shows that Premature Mortality of 45–64 years of age population to slightly increase year over year. Female population premature mortality increases 5.4% and male population at 3.4%. The vertical axis corresponds to DataValueAlt which is in units of 100K cases. For example, there are 485K cases in 2014 for Female (38%) while there are 784K cases in the same year for Male (62%). Surprisingly, I anticipated the premature mortality rate to increase faster for Male over Female overall.
While the correlation pairs gave an overall look, let’s compare indicators between Male and Female populations in the same fashion as by correlation pairs. In the original df_new[‘QuestionID’], there were 201 unique indicators across the dataset. Since we’re focused on the age-adjusted group, our df_new_gender2[‘QuestionID’] shows there are 13 unique indicators. Keeping that in mind, the following analysis shows a subset of indicators. We’re going to start with modifying the dataframe df_new_gender for the following gender and a potential location analysis, thereby dropping several columns that are not useful for this.
df_new_gender2 = df_new_gender.drop(columns=['Question','Topic','DataValueUnit','DataValueType','StratificationCategory1','TopicID','DataValueTypeID','StratificationCategoryID1','StratificationID1','QuestionAbbr'])df_new_gender2.head()
In order to create a table to visualize the correlation pairs by gender, let’s take Table 9a and apply the pivot_table method. Let’s create a dataframe called df_new_gender2_loc_qid where the rows are for each location and columns are each indicator. Since we want to see the data by gender (Stratification1), there needs to be a column with a label Male or Female for each location. Also, since we’re looking at each row by location, it makes sense to summarize the DataValueAlt values for each indicator as an average so we’ll pass in the parameter aggfunc=np.mean into the pivot_table.
As this pivot table is created, I encountered issues with LocationIDs or QuestionIDs that don’t have data, resulting in NA values. When this happens, we can assess whether to keep only rows/columns that have values by dropping all the rows and/or columns with NA values. As I did this, the resulting dataframe was severely reduced. Instead, by using the .fillna() with a mean using df_new_gender_loc_qid.mean(), the NA values are replaced with an average. Table 9b is the top 5 rows of the adjusted pivot table.
df_new_gender2_loc_qid = df_new_gender2.pivot_table(values='DataValueAlt',index=['LocationID','Stratification1'],columns=['QuestionID'],aggfunc=np.mean)# Create pivot table with rows by Location, columns of each QuestionID, values - DataValueAlt mean by Locationdf_new_gender2_loc_qid.reset_index(level='Stratification1',inplace=True)df_new_gender2_loc_qid.fillna(df_new_gender2_loc_qid.mean(),inplace=True)df_new_gender2_loc_qid.head()
With seaborn pairplot on df_new_gender2_loc_qid, the hue parameter allows us to render the plot by gender. Similar to Figure 1 from the Part 2 blog post, we have a plot of each of the 13 indicators along the same X and Y axes. Instead of correlation, we have the average values from DataValueAlt by gender and state (LocationID) for these 13 indicators: ALC6_0 (Alcohol: Chronic Liver Disease Mortality), CKD1_0 (Chronic Kidney Disease: End Stage Renal Disease Mortality), 2 Chronic Obstructive Pulmonary Disease (COPD), 5 Cardiovascular Disease (CVD), 2 Diabetes (DIA), OLD1_0, and OVC5_0 (Overarching Conditions: Premature Mortality Aged 45–64 Years). OVC5_0 came up in the prior sections as a highly correlated indicator of interest.
sns.pairplot(df_new_gender2_loc_qid,hue='Stratification1',palette='RdBu_r')
As mentioned previously about Figure 9 above, OVC5_0 is the Premature Mortality Aged 45–64 Years indicator of interest. Along the bottom row of Figure 9, the pairplot generally shows a distinct distribution between Male (orange) and Female (blue) populations. In nearly all cases when we compare each plot along the bottom row (OVC5_0 to the other indicators), we see Male populations with higher average DataValueAlt values for most locations, which reflects the generally, consistently poorer population health conditions across states for Male.
Stratification Analysis on Adult Premature Mortality —Race
Similar to stratification by gender, the analysis on stratification by race will use the following conditions to filter the data. We’ll create a new dataframe df_new_OVC5_0_race based on OVC5_0, aged based prevalence using “cases per 100,000,” and the StratificationCategory1 of “Race/Ethnicity.”
df_new_OVC5_0_race = df_new[ (df_new['QuestionID'] == 'OVC5_0') & (df_new['DataValueUnit'] == 'cases per 100,000') &(df_new['StratificationCategory1'] == 'Race/Ethnicity')]df_new_OVC5_0_race1 = df_new_OVC5_0_race.groupby(['Stratification1','YearStart']).mean().round(0)df_new_OVC5_0_race1
Further we’ll use the .groupby() method to summarize the subset dataframe df_new_OVC5_0_race into a table based on the race, year, and the DataValueAlt. The categories for race in Stratification1 gives us 5 categories: American Indian or Alaska Native; Asian or Pacific Islander; Black, non-Hispanic; Hispanic; and White, non-Hispanic. For YearStart, we have 5 years of data since this was the result of the available stratification data and the use of the aged-based prevalence. DataValueAlt is an average value of the locations, rounded off to the whole digits. For example,there are 348 cases of Premature Mortality in 45–64 years aged persons per 100K for the Hispanic population in 2010.
To visualize Table 10, seaborn barplot will display each of the race stratifications and the values of DataValueAlt. The parameters for the barplot include passing the x=YearStart, y=DataValueAlt, and hue=Stratification1 from data=df_new_OVC5_0_race. With plt.legend(), we’re able to fine tweak where the legend gets rendered, and I prefer outside plot which uses the bbox_to_anchor parameter.
plt.figure(figsize=(16, 7))sns.barplot(x='YearStart',y='DataValueAlt',data=df_new_OVC5_0_race,hue='Stratification1',ci=None,saturation=0.7)plt.legend(loc='best',bbox_to_anchor=(0.5, 0,.73, .73))
From both Table 10 and Figure 10, the data shows that premature mortality is slightly increasing year over year with incremental increases. Premature mortality of adults aged 45–64 years of age impact the “Black, non-Hispanic” and the “American Indian or Alaska Native” populations most severely. On the other spectrum, we see “Asian or Pacific Islander” and “Hispanic” populations to be least severe. By comparison, we’re seeing the premature mortality numbers for “Black, non-Hispanic” population to be more than double of the “Hispanic” population.
While it’s possible to hypothesize the correlations with the premature mortality numbers for each stratification, it’s beyond the scope of this dataset to tie the descriptive with the causation. Figure 10 gives a general understanding of the seriousness of the factors impacting the overall population, which can be complex and multi-faceted.
Another way of looking at the race stratification is by a stacked bar chart. Again, a pivot_table() with the same data from df_new_OVC5_0_race is a starting point for the bar chart, shown in Table 11.
df_new_OVC5_0_race2 = df_new_OVC5_0_race.pivot_table(values='DataValueAlt',index='YearStart',columns='Stratification1').round()df_new_OVC5_0_race2 
Based on Table 11, we want to create columns of cumulative values instead of the specific values for each stratification. I’m going to modify df_new_OVC5_0_race2 by updating the values for subsequent columns as the sum of the previous columns. For example the values in the column for Asian or Pacific Islander column will be the sum of American Indian or Alaska Native and the Asian or Pacific Islander. Repeat this for the next three columns. With a few additional tweaks of dropping columns to keep the new cumulative columns and resetting the index, the new dataframe df_new_OVC5_0_race2c can be used in the following barplot.
sns.set_style('darkgrid')plt.figure(figsize=(8, 8))sns.barplot(x='YearStart', y='cum_White', data=df_new_OVC5_0_race2c, color='blue',ci=None,saturation=0.7)sns.barplot(x='YearStart', y='cum_Hispanic', data=df_new_OVC5_0_race2c, color='purple',ci=None,saturation=0.7)sns.barplot(x='YearStart', y='cum_Black', data=df_new_OVC5_0_race2c, color='red',ci=None,saturation=0.7)sns.barplot(x='YearStart', y='cum_Asian', data=df_new_OVC5_0_race2c, color='green',ci=None,saturation=0.7)sns.barplot(x='YearStart', y='cum_AmIndianAK', data=df_new_OVC5_0_race2c, color='orange',ci=None,saturation=0.7)
Since the year to year changes for each race stratification are not significantly different, the bars are mostly similar. However, this bar chart gives another view into the overall premature mortality growth from 2010 to 2014. The chart also provides a view of the proportion of the premature mortality amongst the categories of race. The colors of the hues use the same legend as in Figure 10: Blue (White, non-Hispanic), Purple (Hispanic), Red (Black non-Hispanic), Green (Asian Pacific Islander), and Yellow/Orange (American Indian or Alaska Native).
I hope you’ve enjoyed following the reading on the US Chronic Indicators population dataset from the CDC. This wraps up our series. Follow me for more digital health data science related topics. Feel free to suggest any ideas or recommendations!