Analyzing a Decade of Extended Moral Foundations Dictionary (eMFD) Scores for Television News

A quick demonstration of how to analyze moral news framing via GDELT's implementation of the extended Moral Foundations Dictionary.

Frederic R. Hopp
Media Neuroscience Lab - UC Santa Barbara

In [1]:
# Load modules
import seaborn as sns
import pandas as pd
from matplotlib import pyplot as plt
import numpy as np
plt.style.use("seaborn-poster")
plt.style.use("ggplot")

Load in the data

Below we analyze the Television data for CNN, MSNBC, and Fox News.

In [2]:
cnn = pd.read_csv('2020-tvnewsngrams-emfd-5-8-2020-cnn.csv')
msnbc = pd.read_csv('2020-tvnewsngrams-emfd-5-8-2020-msnbc.csv')
fox = pd.read_csv('2020-tvnewsngrams-emfd-5-8-2020-foxnews.csv')
In [3]:
# Define column names
foundations = ['care','harm','fairness','cheating','loyalty','betrayal','authority','subversion','sanctity','degradation']
virtues = ['care','fairness','loyalty','authority','sanctity']
vices = ['harm','cheating','betrayal','subversion','degradation']
base_f = ['care_p','fairness_p','loyalty_p','authority_p','sanctity_p']
sents = ['care_sent','fairness_sent','loyalty_sent','authority_sent','sanctity_sent']

Format the date column to be a python datetime object and set it to the index of the dataframe.

In [4]:
cnn['DATE'] = cnn['DATE'].astype(str)
cnn['DATE'] = cnn['DATE'].apply(pd.to_datetime, format='%Y/%m/%d')
cnn = cnn.set_index('DATE')

msnbc['DATE'] = msnbc['DATE'].astype(str)
msnbc['DATE'] = msnbc['DATE'].apply(pd.to_datetime, format='%Y/%m/%d')
msnbc = msnbc.set_index('DATE')

fox['DATE'] = fox['DATE'].astype(str)
fox['DATE'] = fox['DATE'].apply(pd.to_datetime, format='%Y/%m/%d')
fox = fox.set_index('DATE')

Below we define a function that will calculcate the degree to which a given day of news coverage reflects moral virtues (e.g., care, fairness, loyalty, authority, and subversion) and moral vices (e.g., harm, cheating, betrayal, subversion, degradation).

In simple terms, the function multiples the average moral foundation probabilitiy of a given day with the average foundation sentiment of that day. For example, mulitplying the probabily for care (care_p) with the sentiment for care (care_sent) yields a score that highlights care if the product is positive or harm if the product is negative. Depending on the sign of the product (+/-), the absolute value of the product is added to either a virtue foundation (e.g., + => care) or a vice foundation (e.g., - => harm).

In [5]:
def vice_virtue(df):
    
    df['care_prod'] = df['care_p'] * df['care_sent']
    df['fair_prod'] = df['fairness_p'] * df['fairness_sent']
    df['loy_prod'] = df['loyalty_p'] * df['loyalty_sent']
    df['auth_prod'] = df['authority_p'] * df['authority_sent']
    df['sanct_prod'] = df['sanctity_p'] * df['sanctity_sent']
    
    for f in foundations:
        df[f] = 0.0
        
    for i,row in df.iterrows():
        if row['care_prod'] < 0:
            df.at[i, 'harm'] = np.abs(row['care_prod'])
        else:
            df.at[i, 'care'] = np.abs(row['care_prod'])
        if row['fair_prod'] < 0:
            df.at[i, 'cheating'] = np.abs(row['fair_prod'])
        else:
            df.at[i, 'fairness'] = np.abs(row['fair_prod'])
        if row['loy_prod'] < 0:
            df.at[i, 'betrayal'] = np.abs(row['loy_prod'])
        else:
            df.at[i, 'loyalty'] = np.abs(row['loy_prod'])
        if row['auth_prod'] < 0:
            df.at[i, 'subversion'] = np.abs(row['auth_prod'])
        else:
            df.at[i, 'authority'] = np.abs(row['auth_prod'])
        if row['sanct_prod'] < 0:
            df.at[i, 'degradation'] = np.abs(row['sanct_prod'])
        else:
            df.at[i, 'sanctity'] = np.abs(row['sanct_prod'])
    return df

Before we apply the above function to our dataframe, we normalize (z-transform) the foundation probabilities and sentiment scores for each dataframe.

In [6]:
for f in base_f:
    cnn[f] = (cnn[f] - cnn[f].mean()) / cnn[f].std()
    
for f in base_f:
    msnbc[f] = (msnbc[f] - msnbc[f].mean()) / msnbc[f].std()
    
for f in base_f:
    fox[f] = (fox[f] - fox[f].mean()) / fox[f].std()
    
for s in sents:
    cnn[s] = (cnn[s] - cnn[s].mean()) / cnn[s].std()
    
for s in sents:
    msnbc[s] = (msnbc[s] - msnbc[s].mean()) / msnbc[s].std()
    
for s in sents:
    fox[s] = (fox[s] - fox[s].mean()) / fox[s].std()
In [7]:
# Apply the function
cnn = vice_virtue(cnn)
msnbc = vice_virtue(msnbc)
fox = vice_virtue(fox)

Moving Average (120 days) Time Series of Moral Foundations

In [8]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

cnn[virtues].rolling(120).mean().plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("CNN -- Virtues")
ax[0].set_ylabel('Moral Foundation Prevalence')


cnn[vices].rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("CNN -- Vices")

plt.ylabel('Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('CNN_ts.png',dpi=300)
plt.show()

In [9]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

msnbc[virtues].rolling(120).mean().plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("MSNBC -- Virtues")
ax[0].set_ylabel('Moral Foundation Prevalence')


msnbc[vices].rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("MSNBC -- Vices")

plt.ylabel('Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('MSNBC_ts.png',dpi=300)
plt.show()

In [10]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

fox[virtues].rolling(120).mean().plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("FOX News -- Virtues")
ax[0].set_ylabel('Moral Foundation Prevalence')


fox[vices].rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("FOX News -- Vices")

plt.ylabel('Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('FOX_ts.png',dpi=300)
plt.show()

Similarities and Differences in Moral Framing across Networks

We start by computing the pairwise differences in moral virtues and moral vices. For this, we simply subtract each networks' moral foundation score from the other networks' moral foundation score and take the absolute value (e.g., | cnn_care - fox_care | or | cnn_harm - fox_harm |)

In [11]:
cnn_fox_virtues = np.abs(cnn[virtues].rolling(120).mean() - fox[virtues].rolling(120).mean())
cnn_fox_vices = np.abs(cnn[vices].rolling(120).mean() - fox[vices].rolling(120).mean())
In [12]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

cnn_fox_virtues.plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("Difference Between CNN and FOX in Virtues")
ax[0].set_ylabel('Abolute Difference in Moral Foundation Prevalence')


cnn_fox_vices.rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("Difference Between CNN and FOX in Vices")

plt.ylabel('Abolute Difference in Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('cnn_fox_ts.png',dpi=300)
plt.show()
In [13]:
cnn_msnbc_virtues = np.abs(cnn[virtues].rolling(120).mean() - msnbc[virtues].rolling(120).mean())
cnn_msnbc_vices = np.abs(cnn[vices].rolling(120).mean() - msnbc[vices].rolling(120).mean())
In [14]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

cnn_msnbc_virtues.plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("Difference Between CNN and MSNBC in Virtues")
ax[0].set_ylabel('Absolute Difference in Moral Foundation Prevalence')


cnn_msnbc_vices.rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("Absolute Difference Between CNN and MSNBC in Vices")

plt.ylabel('Absolute Difference in Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('cnn_msnbc_ts.png',dpi=300)
plt.show()
In [15]:
fox_msnbc_virtues = np.abs(fox[virtues].rolling(120).mean() - msnbc[virtues].rolling(120).mean())
fox_msnbc_vices = np.abs(fox[vices].rolling(120).mean() - msnbc[vices].rolling(120).mean())
In [16]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

fox_msnbc_virtues.plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness','Loyalty','Authority','Sanctity'])
ax[0].set_title("Difference Between FOX and MSNBC in Virtues")
ax[0].set_ylabel('Absolute Difference in Moral Foundation Prevalence')


fox_msnbc_vices.rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating','Betrayal','Subversion','Degradation'])
ax[1].set_title("Difference Between FOX and MSNBC in Vices")

plt.ylabel('Absolute Difference in Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('fox_msnbc_ts.png',dpi=300)
plt.show()

Aggregate Difference in Individualizing versus Binding Foundation Across Networks

In [17]:
ind_virtues = cnn_fox_virtues[['care','fairness']] + cnn_msnbc_virtues[['care','fairness']] + fox_msnbc_virtues[['care','fairness']]
ind_vices = cnn_fox_vices[['harm','cheating']] + cnn_msnbc_vices[['harm','cheating']] + fox_msnbc_vices[['harm','cheating']]
In [18]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

ind_virtues.plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Care','Fairness'])
ax[0].set_title("Aggregate Difference in Individualizing Virtue Foundations Across Networks")
ax[0].set_ylabel('Aggregated Difference in Moral Foundation Prevalence')


ind_vices.rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Harm','Cheating'])
ax[1].set_title("Aggregated Difference in Individualizing Vice Foundations Across Networks")

plt.ylabel('Abolute Difference in Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('ind_ts.png',dpi=300)
plt.show()
In [19]:
bind_virtues = cnn_fox_virtues[['loyalty','authority','sanctity']] + cnn_msnbc_virtues[['loyalty','authority','sanctity']] + fox_msnbc_virtues[['loyalty','authority','sanctity']]
bind_vices = cnn_fox_vices[['betrayal','subversion','degradation']] + cnn_msnbc_vices[['betrayal','subversion','degradation']] + fox_msnbc_vices[['betrayal','subversion','degradation']]
In [20]:
fig, ax = plt.subplots(1,2, figsize=(30,8))

bind_virtues.plot(ax=ax[0])
sns.despine(offset=10)
ax[0].legend(loc='upper left', labels=['Loyalty','Authority','Sanctity'])
ax[0].set_title("Aggregate Difference in Binding Virtue Foundations Across Networks")
ax[0].set_ylabel('Aggregated Difference in Moral Foundation Prevalence')


bind_vices.rolling(120).mean().plot(ax=ax[1])
sns.despine(offset=10)
ax[1].legend(loc='upper right', labels=['Betrayal','Subversion','Degradation'])
ax[1].set_title("Aggregate Difference in Binding Vice Foundations Across Networks")

plt.ylabel('Aggregated Difference in Moral Foundation Prevalence')
plt.tight_layout()
plt.savefig('bind_ts.png',dpi=300)
plt.show()