main_python

This file replicates all figures and results from the paper that were produced with Python codes. This file calls preamble.ipynb and uses data from data.dta.

In [1]:
%run preamble.ipynb

Replicate Figure 2

Sender’s Strategy: Commitment vs. Revision, $\rho = 0.8$

In [2]:
UpredictionR = {}
UpredictionR['red']=0
UpredictionR['blue']=0
UpredictionR['none']=0
UpredictionR
UpredictionB = {}
UpredictionB['red']=5/8
UpredictionB['blue']=-5/8
UpredictionB['none']=0
Upredictions = {}
Upredictions['r']= UpredictionR
Upredictions['b']= UpredictionB


VpredictionR = {}
VpredictionR['red']=1
VpredictionR['blue']=np.nan
VpredictionR['none']=-1
VpredictionR
VpredictionB = {}
VpredictionB['red']=np.nan
VpredictionB['blue']=-.75
VpredictionB['none']=+.75
Vpredictions = {}
Vpredictions['r']= VpredictionR
Vpredictions['b']= VpredictionB

col = {'red': 'black', 'blue':'black', 'none':'gray'}

plt.figure(figsize=(15,7))

## The following code plots the histogram
for treatment in ['U80', 'V80']:
    DF = load_data(treatment = treatment, match = 16, role = 'S')
    DF = prepare_data(DF, 'session', 'Strong') 
    plt.subplot(1,2, 1 if treatment[0] == 'U' else 2)
    plt.tight_layout(pad = 6)
    plt.axhline(y=0, color='gray',alpha = .75, linestyle='-')
    xColor = 0
    for color in ['r','b']:
        k = 0
        for msg in ['red', 'blue', 'none']:
            variable1 = '' + color + msg 
            variable2 = 'rev' + color + msg
            variable = - DF[variable1]/100 + DF[variable2]/100
            mean = variable.mean()
            ## Confidence intervals
            if treatment[0] == 'V' and color == 'r' and msg == 'blue': # Ignore the zero probability event: verifiable treatment, red ball & blue message 
                cih, cil = 0, 0 
            elif treatment[0] == 'V' and color == 'b' and msg == 'red': # Ignore the zero probability event: verifiable treatment, blue ball & red message  
                cih, cil = 0, 0 
            else:
                cih, cil = stats.t.interval(0.96, len(variable)-1, loc=np.mean(variable), scale=stats.sem(variable))
            plt.bar(xColor + k, mean, width = .4,  edgecolor= 'black', color= 'rosybrown' if msg == 'red' else  "#34495e" if msg == 'blue' else 'whitesmoke', alpha = 1, label = '$m=' + msg[0] + '$' if color == 'r' else '_nolabel')
            plt.plot([xColor + k, xColor + k],[cih,cil], c='black', alpha = .5)
            k += .5
        xColor += 2
    
    ## Formatting the looks of the plot
    plt.xticks([.5,2.5], [r'$\theta = R$', r"$\theta = B$"], fontsize = 15)
    plt.yticks([-.50, -.25, 0, .25, .50])
    plt.axis([-.5,3.5, -.5,.5])
    plt.title('Treatment $' + treatment +'$', fontsize = 16)
    plt.ylabel(r'$\pi_R(m|\theta) - \pi_C(m|\theta)$', fontsize = 16)
    plt.text(-.5, -.31, 'Informativeness:', fontsize = 14, fontweight='bold')
    corrcomm = round(correlation_bayesian_receiver(DF, '').mean(),2)
    corrrev = round(correlation_bayesian_receiver(DF, 'rev').mean(),2)
    plt.text(-.5 , -.38, '$\phi^B(\pi_C)='+ str(corrcomm)+'$', fontsize = 14)
    plt.text(-.5 , -.45, '$\phi^B(\pi_R)='+ str(corrrev)+'$' , fontsize = 14)
    if treatment == 'U80':
        plt.legend(loc = (0,.76), fontsize = 16)
plt.show()    

Replicate Figure 3

Receiver’s Response to Persuasive Messages: $\rho = 0.2$ vs. $\rho = 1$

In [3]:
from scipy.optimize import curve_fit

## Define polynomial function of degree 2, will be used to fit the data
def quadratic_function(x, a, b, c):
    return a + b * x + c * x ** 2

plt.figure(figsize=(15,6))

for rule in ['U', 'V']: # Compare Unverifiable and Verifiable information in two separate subplots
    plt.subplot(1,2, 1 if rule == 'U' else 2) 
    for treatment in [rule + '20', rule + '100']: # Compare treatments \rho=0.2 and \rho=1 in the same subplot
        msg = 'none' if rule == 'V' else 'red' # Choose the "persuasive" message (see main draft): red for U and none for V
        storem = []
        storeh = []
        storel = []
        DF = load_data(treatment, match = 16, role = 'S')
        DF = prepare_data(DF, 'session', 'Strong') 
        # The next few lines use the variables "post_red" and "post_none", which have been defined in "prepare_data" (see preamble)
        DF = DF[np.isnan(DF['post_' + msg]) == False] # Drop observations for which the persuasive msg has probability zero

        temp = DF.sort_values('post_' + msg, ascending = True) # Generate a temporary dataframe "temp" ordered by post_msg
        x = temp['post_' + msg].values # Generate a vector with post_msg 
        y = temp['m'+ msg[0] + 'dec']  # Generate a vector with receiver's guess to persuasive message 

        ## Estimate Quadratic Fit
        params, covar = curve_fit(quadratic_function, x, y, bounds = [0,1])

        ## Simulate 10'000 times to generate confidence bounds
        rangex = np.linspace(0,1,100) # Range of posterior beliefs 
        temp_par = np.random.multivariate_normal(params, covar, 10000) ## Generate 10'000 parameters from estimation above
        a, b, c = temp_par[:,0],  temp_par[:,1],  temp_par[:,2]
        for x in rangex:
            temp = quadratic_function(x, a, b, c) 
            tempstorem, tempstoreh, tempstorel = np.percentile(temp, 50), np.percentile(temp, 95), np.percentile(temp, 5)
            storeh = np.append(storeh, tempstoreh)
            storel = np.append(storel, tempstorel)
            storem = np.append(storem, tempstorem)
        
        ## Format the looks of the plot
        plt.plot(rangex, storem, '--' if treatment[1:]!='20' else '-', alpha =.7, c = 'black', linewidth = 2, label = '$' + treatment + "$")
        plt.plot(rangex, storel, '--' if treatment[1:]!='20' else '-', alpha =.35, c = 'black', linewidth = 1)
        plt.plot(rangex, storeh, '--' if treatment[1:]!='20' else '-', alpha =.35, c = 'black',  linewidth = 1)
        string = 'r' if rule == 'U' else 'n'
        plt.xlabel('Interim Posterior Conditional on $m=' + string +'$', fontsize = 15)
        plt.axis([0,1,0,1])
    plt.ylabel('Frequency of $a=red$', fontsize = 15)
    plt.title('Unverifiable Information' if treatment[0] == 'U' else 'Verifiable Information', fontsize = 15)
    plt.legend(loc = 2, fontsize = 14)
plt.show()

Replicate Figure 4

Cumulative Distribution of Sender-Average $\phi_B(\pi_C, \pi_R)$ by Treatment

In [4]:
plt.figure(figsize = (16,6))

shades = {'20': 1, '80': 1, '100': .9}
face = {'20': 'white', '80': 'gainsboro', '100': 'black'}

        
for rule in ['U','V']: # Compare Unverifiable and Verifiable information in two separate subplots
    plt.subplot(1, 2, 1 if rule == 'U' else 2)
    for treatment in [rule + '20',  rule + '80', rule + '100']: # Compare treatments with \rho=0.2, \rho=0.8, and \rho=1 in the same subplot
        DF = load_data(treatment, match = 16, role = 'S')
        DF = prepare_data(DF, 'session', 'Strong')
        data = []
        for i in DF.id.unique(): ## Consider each sender separately. 
            temp = DF[DF['id'] == i]
            data_temp = correlation_bayesian_receiver(temp, 'agg')
            data = np.append(data, np.mean(data_temp))  ## Compute sender average
        ## Next lines create CDF and plot
        N = data.size
        Z = np.sort(data)
        F = np.array(range(N)) / float(N)
        plt.plot(Z, F, label = "$"+treatment+"$", c = 'black', alpha = shades[treatment[1:]], marker = 'o', linewidth = .4, markerfacecolor = face[treatment[1:]], markeredgewidth=.5)
        
        ## Format the looks of the plot
        plt.title('Unverifiable Information' if rule == 'U' else 'Verifiable Information', fontsize = 15)
        plt.ylabel('Percentile', fontsize = 15)
        plt.xlabel('Bayesian Correlation $\phi^B(\pi_C,\pi_R)$ by Sender', fontsize = 15)
        plt.yticks([0,.25,.5,.75,1])
        plt.xticks([0,.25,.5,.75,1])
        plt.axis([-.02, 1.02, -0.02, 1.02])
    plt.legend(fontsize = 15)
plt.show()

Replicate Table 3

Average Bayesian Correlations $\phi^B$

In [5]:
for rule in ['U','V']: # Compare Unverifiable and Verifiable information in two separate subplots
    for treatment in [rule + '20',  rule + '80', rule + '100']: 
        DF = load_data(treatment, match = 16, role = 'S')
        DF = prepare_data(DF, 'session', 'Strong')
        mean_corr = round(correlation_bayesian_receiver(DF, 'agg').mean(), 4)
        print(treatment + " " + str(mean_corr))
U20 0.0005
U80 0.3221
U100 0.3389
V20 0.9001
V80 0.8494
V100 0.7751

Figure 6

Sender’s Empirical Expected Payoff

In preparation for the probit estimation, we begin by reshaping the DF. The following code defines a function that reshapes the dataframe DF. In the reshaped dataframe, which we denoted by df, each line corresponds to a single play by a recevier. We invoke the strategy method and define one line for each possible message. If the message had probability zero, the line is dropped. The columns of the reshaped dataframe are

    session subject match   id  posterior   Tot_prob    msg guess   intercept   msg_red msg_blue    msg_none
In [6]:
def reshape_data(DF):
    DF = DF[['session',  'subject', 'match', 'aggpost_red', 'aggpost_blue', 'aggpost_none',  'aggTotprob_red', 
             'aggTotprob_blue', 'aggTotprob_none', 'mrdec', 'mbdec', 'mndec', 'id']]
    vars = [c for c in DF if c.startswith('aggpost')]
    varsTOT = [c for c in DF if c.startswith('aggTot')]
    all = [c for c in DF]
    other = [c for c in all if c not in vars]
    stacked = DF.set_index(other).stack()
    df = stacked.reset_index()
    df.level_10 = np.where(df.level_10 == 'aggpost_red', 'red', df.level_10)
    df.level_10 = np.where(df.level_10 == 'aggpost_blue', 'blue', df.level_10)
    df.level_10 = np.where(df.level_10 == 'aggpost_none', 'none', df.level_10)
    df['msg_post'] = df['level_10']
    del df['level_10']

    all = [c for c in DF]
    other = [c for c in all if c not in (varsTOT + vars)]
    other = other + ['posterior', 'msg_post']
    df['posterior'] = df[0]
    del df[0]

    stacked = df.set_index(other).stack()
    df = stacked.reset_index()
    df['Tot_prob'] = df[0]
    del df[0]
    df.level_9 = np.where(df.level_9 == 'aggTotprob_red', 'red', df.level_9)
    df.level_9 = np.where(df.level_9 == 'aggTotprob_blue', 'blue', df.level_9)
    df.level_9 = np.where(df.level_9 == 'aggTotprob_none', 'none', df.level_9)
    df['msg_tot'] = df['level_9']
    del df['level_9']

    vars  = [c for c in df if c.endswith('dec')]
    all   = [c for c in df]
    other = [c for c in all if c not in vars]
    stacked = df.set_index(other).stack()
    df = stacked.reset_index()

    df.level_8 = np.where(df.level_8 == 'mrdec', 'red', df.level_8)
    df.level_8 = np.where(df.level_8 == 'mbdec', 'blue', df.level_8)
    df.level_8 = np.where(df.level_8 == 'mndec', 'none', df.level_8)
    df['msg_dec'] = df['level_8']
    del df['level_8']
    df = df[df.msg_post == df.msg_tot]
    df = df[df.msg_post == df.msg_dec]
    df['msg'] = df.msg_dec
    df['guess'] = df[0]
    del df['msg_post'], df['msg_tot'], df['msg_dec'], df[0]
    df = df.reset_index()
    df['intercept'] = 1
    for message in ['red', 'blue', 'none']:
        df['msg_' + message] = np.where(df.msg == message, 1, 0)
    return df

The following functions are used in the Probit estimation

In [7]:
## The following function computes the Bayesian posterior that is induced by strategy(x) (defined later). 
#  The Bayesian posterior for messages that have 0 probability is set to NaN
def posterior(strategy):
    store = [-1, -1, -1]
    for i in [0, 1, 2]:
        totprob = 1/3 * strategy[i] + 2/3 * strategy[i + 3] # \mu(R) + \pi(i|R) + \mu(B) + \pi(i|B)
        if totprob > 0:
            store[i] = 1/3 * strategy[i] / totprob
        else:
            store[i] = np.nan
    return store

## The following function estimates Probit for receiver "id". 
#  The model is alpha + beta * posterior + gamma * msg_red + delta * msg_blue and outputs the vector of coefficients
def probit(verbose, id):
    dfreg = df[df.id == id]
    inter = ['intercept']
    result = sm.Probit(dfreg.guess, dfreg[inter + ['posterior', 'msg_red', 'msg_blue']]).fit(maxiter = 200, method = 'bfgs', disp = (verbose == 'Yes'))
    coeff = result.params.values
    if verbose == 'Yes':
        print(result.summary())
    return coeff


## The following function computes the following Probit linear model: 
#  Phi(intercept + beta * Bayesian posterior  + gamma * dummy(msg is red) + delta * dummy(msg is blue)
#    where Phi denotes the normal CDF
#    x denotes the Bayesian posterior at a given message msg
def yhat(x, msg, coeff):

    if np.isnan(x):
        return np.nan 
    else:
        xhat = np.dot(coeff[0 : 4], ([1, x, msg == 'red', msg == 'blue']))
        return norm.cdf(xhat) # Normal CDF

    
## The following function computes the expected payoff given a strategy(x) in a certain treatment and a receiver's avg_response     
#  The expected payoff is expressed in dollars, hence it has a multiplier of 2 since the sender wins $2 per round when the receiver guesses red 
def expectedpayoff(strategy, treatment, avg_response):
    return 2 * ((1/3 * strategy[treatment][0] + 2/3 * strategy[treatment][3]) * np.nan_to_num(avg_response['red']) + 
            (1/3 * strategy[treatment][1] + 2/3 * strategy[treatment][4])* np.nan_to_num(avg_response['blue']) + 
            (1/3 * strategy[treatment][2] + 2/3 * strategy[treatment][5])* np.nan_to_num(avg_response['none']) 
            )

This last piece of code runs the estimation algorithm and plots the figure

This routine produces warnings because there are few receivers who always guess blue. For these receiver, the Probit estimation outputs a warning. Despite the warning, however, the coefficients that are recorded are correct in the sense that these receivers are estimated to guess red for any posterior x with probability zero, as it should be.

In [8]:
import warnings
warnings.simplefilter('ignore')

# The previous code silence the warnings. This routine produces warnings because there are few receivers 
# who always guess blue. For these receiver, the Probit estimation outputs a warning. Despite the warning, 
# however, the coefficients that are recorded are correct in the sense that these receivers are estimated 
# to guess red for any posterior x with probability zero, as it should be. 

import statsmodels.discrete.discrete_model as sm
from scipy.stats import norm


rangex = np.linspace(0, 1, 50)
plt.figure(figsize = (8,6))

for treatment in ['U100','V100']:
    paystore = []
    paystore_pred = []
    
    for x in rangex: ## For loop a relevant class of strategies parameterized by x (see main draft)
        strategy = {'U100' : [1, 0, 0, 1 - x, x, 0], 
                    'V100' : [0, 0, 1, 0, x, 1-x]} 
        DF = load_data(treatment, 16, 'R') ## Select the database of receivers
        DF = prepare_data(DF, '', '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank 
        df = reshape_data(DF)
        df['id'] = df.id.astype('int')

        intercept = 'Yes'
        phistore = []
        ESstore = []
        
        for id in df.id.unique(): ## For every receiver
            coeff = probit(verbose = 'No', id = id) ## Estimate probit coefficients for each receiver
            avg_response = {'red': yhat(posterior(strategy[treatment])[0], 'red', coeff), 
                            'blue': yhat(posterior(strategy[treatment])[1], 'blue', coeff),                 
                            'none': yhat(posterior(strategy[treatment])[2], 'none', coeff) }
            ES  = expectedpayoff(strategy, treatment, avg_response)
            ESstore = np.append(ESstore, ES)
        paystore = np.append(paystore, np.median(ESstore)) # Median
        
        ## Compute predictions: receiver's best respond to strategy(x); compute sender's expected payoff
        temp = np.array(posterior(strategy[treatment])) # Posteriors that strategy(x) induces
        ## receiver's best response, breaking ties in favor of sender
        avg_response_pred = {'red': (temp >=.5)[0], 
                                    'blue': (temp >=.5)[1],                 
                                    'none': (temp >=.5)[2]}     
        
        ## Next line computes sender's predicted expected payoff (2 dollars times probability of guessing red)
        payoff_pred = 2 * ((1/3 * strategy[treatment][0] + 2/3 * strategy[treatment][3]) * np.nan_to_num(avg_response_pred['red']) + 
                           (1/3 * strategy[treatment][1] + 2/3 * strategy[treatment][4])* np.nan_to_num(avg_response_pred['blue']) + 
                           (1/3 * strategy[treatment][2] + 2/3 * strategy[treatment][5])* np.nan_to_num(avg_response_pred['none'])) 
        
        # store payoff_pred
        paystore_pred = np.append(paystore_pred,  payoff_pred) 
    ## --> end of x loop <--
    
    
    ## Plot and format the looks:
    if treatment == "U100":
        plt.plot(rangex, paystore[:] , c = 'black', marker='o',  markevery=3, markeredgecolor='black', markeredgewidth = 1, markerfacecolor='white', markersize=8,   linestyle = "dashed", alpha = 1, label =  "$" + treatment +  "$")
    if treatment == "V100":
        plt.plot(rangex, paystore[:] , c = 'gray', marker='s', markevery=3,  markeredgecolor='gray', markerfacecolor='gray', markersize=6, alpha = 1, label = "$"+treatment+ "$")
        plt.scatter(rangex, paystore_pred[:] , c = 'black',  s=13, alpha = .75, marker='o',label = "Predictions")
    plt.xlabel("$x$ -- Parametrized Sender's Strategy", fontsize = 15)
    plt.ylabel("Sender's Expected Payoff", fontsize = 15)
    plt.legend(loc = 2, prop = {'size' : 15})
    plt.axis([0,1,-0.01,1.35])
    plt.yticks([0,.25,.5,.75,1,1.25])
    plt.xticks([0,.25,.5,.75,1])

    
plt.show()

Replicate Figure 7

Treatment U80 – Clustering of Senders’ Strategies

In [9]:
treatment = "U80"
n = 4 # Number of clusters to estimate

DF = load_data(treatment, match = 16, role = 'S')
DF = prepare_data(DF, 'subject', 'Weak') ## Use imputation method (subject-weak), see Footnote 32
DF["corr_agg"] = correlation_bayesian_receiver(DF, 'agg')

np.random.seed(1)
   
colors = ['orange','r', 'b','g']
markers = ['^','o','s',"D"]


df, AS = clusters(DF, n) # Computes clusters and stores them

relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,0))

fig = plt.figure(figsize=(16,6))

## Commitment Stage Strategy
plt.subplot(1,2, 1)
plt.axhline(0, c = 'black', alpha = 1, linewidth = 1.3, zorder = -1)
plt.axvline(0, c = 'black', linewidth = 1.3, zorder=0)
plt.scatter(100, 5/8*100, c = 'white', s = 200, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1)
for i in range(n): # Plot observed commitment strategies 
    plt.scatter(df[df.cluster==i].rred, df[df.cluster==i].bblue, c = colors[i], s = 25, alpha = .25, edgecolors=  colors[i], marker = markers[i], label = "_nolabel")
for i in range(n): # Plot cluster centroids
    plt.scatter(AS[i, 0], AS[i, 3], c = colors[i], s = 150, alpha = .75, edgecolors=  colors[i], marker = markers[i], linewidth = 2)
plt.axis([-5,105,-5,105])
plt.xlabel('$\pi_{C}(r|R)$', fontsize = 18)
plt.ylabel('$\pi_{C}(b|B)$', fontsize = 18)
plt.title("$U80$ -- Commitment Stage", fontsize = 18)


## Revision Stage Strategy
plt.subplot(1,2, 2)    
plt.axhline(0, c = 'black', alpha = 1, linewidth = 1.3, zorder = -1)
plt.axvline(0, c = 'black', linewidth = 1.3, zorder=0)
plt.title("$U80$ -- Revision Stage", fontsize = 18)
sns.despine()
plt.axis([-5,105,-5,105])
plt.xlabel('$\pi_{R}(r|R)$', fontsize = 18)
plt.ylabel('$\pi_{R}(b|B)$', fontsize = 18)
plt.scatter(100, 0, c = 'white', s = 200, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1, label = "Prediction, $\phi^B = 0.50$")
for i in [2,1,3,0]: # Plot observed revision strategies 
    plt.scatter(df[df.cluster==i].revrred, df[df.cluster==i].revbblue, c = colors[i], s = 25, alpha = .25, edgecolors=  colors[i], marker = markers[i],  label = "_nolabel")
for i in [2,1,0,3]: # Plot cluster centroids
    plt.scatter(AS[i, 4], AS[i, 7], c = colors[i], s = 150, alpha = .75, edgecolors=  colors[i], marker = markers[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
plt.legend(loc = 'upper right', ncol=5, fontsize = 14, bbox_to_anchor=(1.1, -0.15))
plt.show()



    

Replicate Figure 8

Treatment V80 – Clustering of Senders’ Strategies

In [10]:
treatment = "V80"
n = 4 # Number of clusters to estimate

DF = load_data(treatment, match = 16, role = 'S')
DF = prepare_data(DF, 'subject', 'Weak')  ## Use imputation method (subject-weak), see Footnote 32
DF["corr_agg"] = correlation_bayesian_receiver(DF, 'agg')

np.random.seed(1)

df, AS = clusters(DF, n) # Computes clusters and stores them

colors  = ['orange','r', 'orchid','b']
markers = ['^','o','*',"s"]

relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,0))


fig = plt.figure(figsize=(16,6))

## Commitment Stage Strategy
plt.subplot(1,2, 1)    
plt.scatter(0, 3/4*100, c = 'white', s = 200, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1)
plt.axis([-5,105,-5,105])
plt.xlabel('$\pi_{C}(r|R)$', fontsize = 18)
plt.ylabel('$\pi_{C}(b|B)$', fontsize = 18)
plt.axvline(0, c = 'gray', linewidth = 1)
plt.axhline(0, c = 'gray', linewidth = 1)
plt.title("$V80$ -- Commitment Stage", fontsize = 18)
for i in range(n):  # Plot observed commitment strategies 
    plt.scatter(df[df.cluster==i].rred, df[df.cluster==i].bblue, c = colors[i], s = 25, alpha = .25, edgecolors=  colors[i], marker = markers[i])
for i in range(n): # Plot cluster centroids
    plt.scatter(AS[i, 0], AS[i, 3], c = colors[i], s = 150  if i!=2 else 250, alpha = .75, edgecolors=  colors[i], marker = markers[i], linewidth = 2)

## Revision Stage Strategy
plt.subplot(1,2, 2)    
plt.scatter(100, 0, c = 'white', s = 200, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1, label = "Prediction, $\phi^B = 0.57$")
plt.axis([-5,105,-5,105])
plt.xlabel('$\pi_{R}(r|R)$', fontsize = 18)
plt.ylabel('$\pi_{R}(b|B)$', fontsize = 18)
plt.axvline(0, c = 'gray', linewidth = 1)
plt.axhline(0, c = 'gray', linewidth = 1)
for i in [2,1,3,0]:  # Plot observed revision strategies 
    plt.scatter(df[df.cluster==i].revrred, df[df.cluster==i].revbblue, c = colors[i], s = 25, alpha = .25, edgecolors=  colors[i], marker = markers[i], label = '_nolabel')
for i in [3,1,2,0]: # Plot cluster centroids
    plt.scatter(AS[i, 4], AS[i, 7], c = colors[i], s = 150 if i!=2 else 250, alpha = .75, edgecolors =  colors[i], marker = markers[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
plt.title("$V80$ -- Revision Stage", fontsize = 18)
plt.legend(loc = 'upper right', ncol=5, fontsize = 14, bbox_to_anchor=(1.1, -0.15))

plt.show()

Replicate Table 4

QRE-Implied Correlations

We begin by defining the functions that we will use in the main routine

In [11]:
# The following function creates data about the frequency.
def freq(DF):
    df_foo = DF
    freq_s = np.full([n, 1], np.nan) # Initialize matrices
    avg = np.full([n, 3], np.nan) # Initialize matrices
    for i in range(0, n):
        freq_s[i] = sum(df_foo.cluster ==  i) # Compute empirical frequency of each cluster, i.e. mixed action of the sender
        avg[i, 0] = df_foo[df_foo.cluster == i].mrdec.mean() # Compute empirical freq for receiver, i.e. mixed action of receiver
        avg[i, 1] = df_foo[df_foo.cluster == i].mbdec.mean() 
        avg[i, 2] = df_foo[df_foo.cluster == i].mndec.mean() 
    # The empirical frequency of observing a message [j] coming from cluster [i] turning into a:
    #Red guess 
    freq_r_RED = freq_s * Tot_prob * avg
    #Blue guess
    freq_r_BLUE = freq_s * Tot_prob * (1 - avg)
    return (freq_s, freq_r_RED, freq_r_BLUE)


def MLE(DF, _lambda_):  
    #Rename dataframe with a temporary name
    df_foo = DF
    
    ## Initialize empty matrices
    PR = np.full([n, 3], np.nan)
    PS = np.full([n, 1], np.nan)
 
    # Compute expected payoffs
    # RECEIVER 
    # Probability of guess Red given cluster i and message, computed with Logit model parameter _lambda_  
    PR = np.exp(_lambda_ * ER) / (np.exp(_lambda_ * ER) + np.exp(_lambda_ * (2 - ER)))    # Colums: 0 msg red, 1 blue, 2 none

    # SENDER 
    # Probability of sender using cluster i, computed with Logit model parameter _lambda_ and empirical ES. 
    PS = np.exp(_lambda_ * ES) / np.nansum(np.exp(_lambda_ * ES)) 

    # Loglikelihoods: computes the loglikelihood. log(Pr(Red|r,cluster[i])) * Freq(r,cluster[i],Red)
    LR = np.nansum((np.log(PR) * freq_r_RED +  np.log(1 - PR) * freq_r_BLUE))
    LS = np.nansum(freq_s *  np.log(PS))               
    return (LS, LR)


# The following function is a "vectorized" version of MLE,
# It takes vectors of lambdas and outputs vectors of loglikelihoods
def MLE_vect(DF, lambdavector):
    LS = np.full([np.size(L), 1], np.nan)
    LR = np.full([np.size(L), 1], np.nan)
    for i in range(0, np.size(L)):
        LS[i], LR[i] = MLE(DF, lambdavector[i])
    return LS, LR


# The following function maximizes the loglikelihood
def QRE(DF, lambdavector):
    LS, LR = MLE_vect(DF, lambdavector)
    lambdaS = L[np.argmax(LS)]
    lambdaR = L[np.argmax(LR)]
    return (lambdaS, lambdaR) 



def load_all(match, treatments):
    k = 1
    for treatment in treatments:
        DF = load_data(treatment, match, role = 'S')
        DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
        if k == 1:
            DFcommon = DF
        else:
            DFcommon = pd.concat([DFcommon, DF])
        del DF
        k = 1 + k
    return DFcommon


def datasetup_all(treatments, match, n, target_treatment):

    DF = load_all(match, treatments) 
    DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
    DF, centroids = clusters_full_commitment(DF, n)

    DF = DF[DF.treatment == target_treatment]
    # We next define matrices as function of clusters that will be used in QRE computation
    # Declare space
    Tot_prob = np.full([n, 3], np.nan)
    Posterior = np.full([n, 3], np.nan)
    ER = np.full([n, 3], np.nan)
    ES = np.full([n, 1], np.nan)
    for i in range(n):
        for j in [0, 1, 2]: # [red blue none]
            # Given cluster, computes the total prob of sending message r, m, n.
            Tot_prob[i, j] = (1/3 * centroids[i, j]  + 2/3 * centroids[i, j + 3] )/100
            # Given cluster, computes the implied posterior conditional on receiving message r, m, n.
            Posterior[i, j] = 1/3 * centroids[i, j] / Tot_prob[i, j] /100
            # Given cluster, computes the implied posterior conditional on receiving message r, m, n.
        ES[i] = 2 * (Tot_prob[i, 0]  * DF[DF.cluster == i].mrdec + Tot_prob[i, 1] * DF[DF.cluster == i].mbdec +  Tot_prob[i, 2] * DF[DF.cluster == i].mndec).mean()
    # Expected payoff for the receiver if she guesses Red.
    ER = 2 * Posterior   
    return DF, centroids, Tot_prob, Posterior, ES, ER

The following cell defines a function that simulates a dataframe with N observations

In [12]:
def DGP(df, lambdaS, lambdaR, N, ES, ER, Posterior, Tot_prob, AS): 
    n = len(AS[:, 0])
    ## Initialize empty matrices
    PR = np.full([n, 3], np.nan)
    PS = np.full([n, 1], np.nan)
    exponential = np.full([n, 1], np.nan)
    DF_sim = np.full([N, 6], np.nan)

    # Compute objective expected payoffs
    for i in range(n):
        # RECEIVER 
        for message in [0, 1, 2]: # 0 means red, 1 means blue, 2 means none
            # Logit form to compute probability of action R given action i and message m=[0,1,2] 
            PR[i, message] = np.exp(lambdaR * ER[i, message]) / (np.exp(lambdaR * ER[i, message]) + np.exp(lambdaR * (2 - ER[i, message])))
       # SENDER
        exponential[i] = np.exp(lambdaS * ES[i])
    for i in range(n):
        PS[i] = exponential[i] / (np.nansum(exponential))
    # end of SENDER
    theta = np.random.choice(2, size = N, p=[2/3, 1/3])
    # Theta = 0 => blue    # Theta = 1 => red
    a_s = np.random.choice(n, size = N, p=np.nan_to_num(PS).T[0])
    #####
    # These next lines of code are a cumbersome way to generate random data using matrix algebra instead of a FOR loop. 
    # This makes the code much faster
    
    # Generates variable message
    temp0 = np.stack((AS[a_s, 3 * (1 - theta)], AS[a_s, 3 * (1 - theta) + 1], AS[a_s, 3 * (1 - theta) + 2])).T
    temp1 = temp0.cumsum(axis=1)
    temp2 = np.random.rand(len(temp1), 1)*100
    m = (temp2 < temp1).argmax(axis=1)


    # Generates variable mrdec mbdec mndec
    d = {}
    for message in [0, 1, 2]:
        temp0 = np.stack((1 - PR[a_s, message], PR[a_s, message])).T
        temp1 = temp0.cumsum(axis=1)
        temp2 = np.random.rand(len(temp1), 1)
        d["a_r{0}".format(message)] = (temp2 < temp1).argmax(axis=1)
    ####
    # Let's put all together in a big matrix
    DF_sim[:,0] = theta
    DF_sim[:,1] = m
    DF_sim[:,2] = a_s
    DF_sim[:,3] = d["a_r0"]
    DF_sim[:,4] = d["a_r1"]
    DF_sim[:,5] = d["a_r2"]
    # Translate matrix into a Pandas dataframe
    DF_sim = pd.DataFrame(DF_sim, columns=['theta', 'message', 'cluster', 'mrdec', 'mbdec', 'mndec']).astype(int)
    
    # Adds one particular realization of a "session"
    DF_sim['decision'] = np.where(DF_sim['message']==0, DF_sim['mrdec'], 0 ) + np.where(DF_sim['message']==1, DF_sim['mbdec'], 0 ) +  np.where(DF_sim['message']==2, DF_sim['mndec'], 0 )
    DF_sim['pay_R']    = np.where(DF_sim.decision == DF_sim.theta, 2, 0)
    DF_sim['pay_S']    = np.where(DF_sim.decision == 1, 2, 0)

    corr = np.corrcoef(DF_sim.theta,DF_sim.decision)[0,1]
    return (DF_sim, corr)

Main QRE routine

In [13]:
treatments = ['V100', 'U100']
n = 4 # Number of clusters
match = 16 # Focus on last 10 rounds
N = 10000 # N is the number of observations in the simulated dataframe (see DGP())
K = 30 # Run the same routine K times to integrate the uncertainty created by the starting point of the kMeans algorithm  

L = np.linspace(0, 7.5, 200) # Space for the maximization of lambdaR and lambdaS

for treatment in treatments:
    storeS = []
    storeR = []
    storeC_act = []
    storeC_bay = []
    for i in range(0, K): # Randomize over the seed, so that I eliminate the uncertainty created by the starting point of the clustering algorithm.  
        np.random.seed(i)
        DF, centroids, Tot_prob, Posterior, ES, ER = datasetup_all([treatment], match, n, treatment)
        (freq_s, freq_r_RED, freq_r_BLUE) = freq(DF)
        LS, LR = QRE(DF, L)
        
        # The following line simulates a dataframe assuming senders and receivers behave according to lambdaS and lambdaR
        # From this dataframe we compute the correlation phi -- corr_actual
        _, corr_actual = DGP(DF, LS, LR , N, ES, ER, Posterior, Tot_prob, centroids)

        # The following line simulates a dataframe assuming that senders behaves according to lambdaS and that receivers is Bayesian
        # It is convenient to approximate Bayesian behavior by setting lambdaR to an arbitrary high number
        # From this dataframe we compute the bayesian correlation phi^B -- corr_bayesian    
        _, corr_bayesian = DGP(DF, LS, 50 , N, ES, ER, Posterior, Tot_prob, centroids)
        storeS = np.append(storeS, LS) 
        storeR = np.append(storeR, LR) 
        storeC_act = np.append(storeC_act, corr_actual) 
        storeC_bay = np.append(storeC_bay, corr_bayesian)
        
    print(treatment)
    print("lambda_R: " + str(round(np.nanmean(storeR),2)) + str(" (footnote 37)"))
    print("lambda_S: " + str(round(np.nanmean(storeS),2)) + str(" (footnote 37)"))
    print("QRE-Implied Bayesian Correlation: " + str(round(np.nanmean(storeC_bay),3)))
    print("QRE-Implied Actual Correlation: " + str(round(np.nanmean(storeC_act),2)))
    print("")
V100
lambda_R: 1.68 (footnote 37)
lambda_S: 0.41 (footnote 37)
QRE-Implied Bayesian Correlation: 0.717
QRE-Implied Actual Correlation: 0.64

U100
lambda_R: 1.28 (footnote 37)
lambda_S: 0.21 (footnote 37)
QRE-Implied Bayesian Correlation: 0.415
QRE-Implied Actual Correlation: 0.26

Appendix

Replicate Figure B9

Strategy Clusters and CDFs of Posterior Divergence $\psi^B$

In [14]:
np.random.seed(1)
treatment = 'U100H'
q = 3/4 # Persuasion threshold
n = 4   # Number of clusters

## This function computes the bayesian correlation for treatments whose persuasion threshold q is not necessarily 1/2
def correlation_bayesian_receiver_q(DF_foo, stage, q):
    if stage == 'comm':
        stage = ""
    n11 = 1 / (3 * 100) * (DF_foo['{}rred'.format(stage)] * (DF_foo['{}post_red'.format(stage)] >= q) + DF_foo['{}rblue'.format(stage)]  * (DF_foo['{}post_blue'.format(stage)]  >= q) + DF_foo['{}rnone'.format(stage)] * (DF_foo['{}post_none'.format(stage)] >= q))
    n10 = 1 / (3 * 100) * (DF_foo['{}rred'.format(stage)] * (DF_foo['{}post_red'.format(stage)]  < q) + DF_foo['{}rblue'.format(stage)]  * (DF_foo['{}post_blue'.format(stage)]  < q) + DF_foo['{}rnone'.format(stage)] * (DF_foo['{}post_none'.format(stage)] < q) )
    n00 = 2 / (3 * 100) * (DF_foo['{}bred'.format(stage)] * (DF_foo['{}post_red'.format(stage)]  < q) + DF_foo['{}bblue'.format(stage)]  * (DF_foo['{}post_blue'.format(stage)]  < q) + DF_foo['{}bnone'.format(stage)] * (DF_foo['{}post_none'.format(stage)] < q))
    n01 = 2 / (3 * 100) * (DF_foo['{}bred'.format(stage)] * (DF_foo['{}post_red'.format(stage)]  >= q) + DF_foo['{}bblue'.format(stage)] * (DF_foo['{}post_blue'.format(stage)]  >= q) + DF_foo['{}bnone'.format(stage)] * (DF_foo['{}post_none'.format(stage)] >= q))         
    phi = (n11 * n00 - n01 * n10) / np.sqrt((n00 + n01) * (n10 + n11) * (n11 + n01) * (n10 + n00))
    return np.nan_to_num(phi)


DF = load_data(treatment, match = 16, role = 'S')
DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
DF, AS = clusters_full_commitment(DF, n) 
DF["corr_agg"] = correlation_bayesian_receiver_q(DF, 'agg', q)
df = DF
AS = AS/100
relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,1))

colz = ['orchid','b', 'g','r', 'orange', 'pink']
marz = ['*','s','D',"o","p","o"]
lab = relsize

plt.figure(figsize=(7,6))
plt.scatter(1, 5/6, c = 'white', s = 150, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1, label = "Prediction, $\phi^B = 0.79$")

for i in [1,0,3, 2]:
    plt.scatter(AS[i,0], AS[i,4], c = colz[i], s = 120, alpha = .75, edgecolors=  colz[i], marker = marz[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
    plt.xlabel('$\pi_C(r|R)$', fontsize = 15)
    plt.ylabel('$\pi_C(b|B)$', fontsize = 15)
    plt.axis([-.01, 1.02, -.01, 1.01])
plt.xticks([0, .25, .50, .75,1.00])
plt.yticks([0, .25, .50, .75,1.00])
plt.title("Clusters for Treatment $" + treatment +"$", fontsize = 15)
plt.legend(loc = (0.05,-.3), fontsize = 12, ncol=2)
plt.show()





plt.figure(figsize = (7,6))
for treatment in ['U100', 'U100H']:
    DF = load_data(treatment, match = 16, role = 'S')
    DF = prepare_data(DF, '', '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
    data = []
    for i in DF.id.unique():
        temp = DF[DF['id'] == i] ## Compute sender-specific 
        psi_B_temp = (np.nan_to_num(temp.aggpost_red)**2* temp.aggTotprob_red + np.nan_to_num(temp.aggpost_blue)**2* temp.aggTotprob_blue  + np.nan_to_num(temp.aggpost_none**2) * temp.aggTotprob_none) - (1/3)**2
        psi_B_temp = psi_B_temp/(1/3*2/3)
        data = np.append(data, np.mean(psi_B_temp))
    
    print(treatment)
    print("Posterior Divergence $\psi^B$: " + str(np.round(np.mean(data),2)))
    print("")
    
    # Posterior Variance
    N = data.size
    Z = np.sort(data)
    F = np.array(range(N)) / float(N)
    shades = {'20': 1, '100H': 1, '100': .9}
    face = {'20': 'white', '100H': 'white', '100': 'black'}
    plt.plot(Z, F, label = "$"+treatment+"$", c = 'black', alpha = shades[treatment[1:]], marker = 'o', linewidth = .4, markerfacecolor = face[treatment[1:]], markeredgewidth=.5)
    plt.ylabel('Percentile', fontsize = 15)
    plt.xlabel('Posterior Divergence $\psi^B$ by Sender', fontsize = 15)
    plt.title('CDF of Posterior Divergence $\psi^B$', fontsize = 15)
    plt.yticks([0,.25,.5,.75,1])
    plt.axis([-.025, 1.02, -0.02, 1.02])
plt.legend(loc = (0.2,-.2), fontsize = 15, ncol=2)
plt.text(0.1,-.325, '.', color='w', fontsize=14)

plt.show()
U100
Posterior Divergence $\psi^B$: 0.3

U100H
Posterior Divergence $\psi^B$: 0.42

Replicate Figure B10 (left panel)

Senders’ clustered bheavior in U100 vs U100S

In [15]:
n = 4 ## Number of clusters


## Plot clusters for treatment U100S
colors  = ['b','r', 'g','orange', 'orange', 'pink']
markers = ['s','o','D',"^","p","o"]
np.random.seed(0)
DF = load_data(treatment = 'U100S', match = 16, role = 'S')
DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
df, AS = clusters_full_commitment(DF, n) 
df["corr_agg"] = correlation_bayesian_receiver(df, 'agg')
AS = AS/100
relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,1))
lab = relsize

plt.figure(figsize=(7,6))
for i in [0,3,1, 2]:
    plt.scatter(AS[i,0], AS[i,4], c = colors[i], s = 120, alpha = .75, edgecolors=  colors[i], marker = markers[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
    plt.xlabel('$\pi_C(r|R)$', fontsize = 15)
    plt.ylabel('$\pi_C(b|B)$', fontsize = 15)
    plt.axis([-.01, 1.01, -.01, 1.01])
plt.xticks([0, .25, .50, .75,1.00])
plt.yticks([0, .25, .50, .75,1.00])
plt.title("Clusters in Treatment $U100S$ (solid) and $U100$ (hollow)", fontsize = 15)

## Plot clusters for treatment U100
colors  = ['b','orange', 'r','g', 'orange', 'pink']
markers = ['s','^','o',"D","p","o"]
np.random.seed(0)
DF = load_data('U100', match = 16, role = 'S')
DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank
df, AS = clusters_full_commitment(DF, n) 
df["corr_agg"] = correlation_bayesian_receiver(df, 'agg')
AS = AS/100
relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,1))
for i in [ 0, 1, 2 , 3 ]:
    plt.scatter(AS[i,0], AS[i,4], c = 'white', s = 120, alpha = .75, edgecolors=  colors[i], marker = markers[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))

plt.legend(loc = (0.05,-.35), fontsize = 12, ncol=2)
plt.show()

Replicate Figure B10 (right panel)

How receivers' behavior compares in U100 vs U100S

In [16]:
## The following function creates a dataframe with the average empirical probability that a receiver guesses Red if the Bayesian posterior induced by the sender belongs to a certain bin. 

n = 4 ## Number of "bins" in the posterior belief [0,1]
k = 1 
def reshape_average_guess(treatment):
    store_df = pd.DataFrame(index=range(n), columns=['a'])
    DF = load_data(treatment, match = 16, role = 'S')
    DF = prepare_data(DF, '', '')  # imputation method is irrelevant since this is is a full-commitment treatment, left blank
    post = np.array([])    
    dec  = np.array([])    
    msg  = np.array([])    
    post = np.append(post, DF.aggpost_red.values)
    post = np.append(post, DF.aggpost_blue.values)
    post = np.append(post, DF.aggpost_none.values)
    dec  = np.append(dec, DF.mrdec.values)
    dec  = np.append(dec, DF.mbdec.values)
    dec  = np.append(dec, DF.mndec.values)
    msg  = np.append(msg, 0 * np.ones(DF.mrdec.size))
    msg  = np.append(msg, 1 * np.ones(DF.mrdec.size))
    msg  = np.append(msg, 2 * np.ones(DF.mrdec.size))
    temp = np.isnan(post) == False
    dec = dec[temp]
    post = post[temp]
    msg = msg[temp]
    post = np.where(post > 1, 1, post)
    temp_df = pd.DataFrame(sorted(zip(post,dec, msg)), columns=('post', 'dec', 'msg'))
    temp = pd.cut(temp_df.post, n)
    temp_df['new'] = temp
    clusters = temp.unique()
    temp_df['bin'] = -10
    for i in range(temp_df.post.size):
        temp_df.loc[i, 'bin'] = np.argmax(clusters == temp_df.new[i])
    temp_df['counter'] = 1 
    grouped_bin = temp_df.groupby(temp_df.bin)
    grouped_bin_and_msg = temp_df.groupby([temp_df.bin, temp_df.msg])
    temp_df['freq_post'] = grouped_bin['counter'].transform(lambda x: x.sum())
    temp_df['mean_dec_per_msg']  = grouped_bin_and_msg['dec'].transform(lambda x: x.mean())
    temp_df['freq_post_per_msg']  = grouped_bin_and_msg['counter'].transform(lambda x: x.sum())
    temp_df['mean_dec']  = grouped_bin['dec'].transform(lambda x: x.mean())
    df = grouped_bin_and_msg.mean()
    df['msg'] = df.index.get_level_values('msg')
    df['bin'] = df.index.get_level_values('bin')
    df = df.reset_index(drop=True)
    store_df['freq_post'] = df.pivot(index='bin', columns='msg', values=('freq_post'))[1.0]
    store_df['mean_dec'] = df.pivot(index='bin', columns='msg', values=('mean_dec'))[1.0]
    store_df['mean_dec_r'] = df.pivot(index='bin', columns='msg', values=('mean_dec_per_msg'))[0.0]
    store_df['mean_dec_b'] = df.pivot(index='bin', columns='msg', values=('mean_dec_per_msg'))[1.0]
    if treatment != 'U100S':
        store_df['mean_dec_n'] = df.pivot(index='bin', columns='msg', values=('mean_dec_per_msg'))[2.0]
    store_df['post_freq_r'] = df.pivot(index='bin', columns='msg', values=('freq_post_per_msg'))[0.0]
    store_df['post_freq_b'] = df.pivot(index='bin', columns='msg', values=('freq_post_per_msg'))[1.0]
    if treatment != 'U100S':
        store_df['post_freq_n'] = df.pivot(index='bin', columns='msg', values=('freq_post_per_msg'))[2.0]
#     store_df['treatment'] = treatment
    store_df.reset_index(inplace=True)
    ## PLOTTING
    store_df['label']= ' '
    store_df.loc[0, 'label'] = '[0.00, 0.25]'
    store_df.loc[1, 'label'] = '(0.25, 0.50]'
    store_df.loc[2, 'label'] = '(0.50, 0.75]'
    store_df.loc[3, 'label'] = '(0.75, 1.00]'
#     store_df.loc[4, 'label'] = '(0.80, 1.00]'
    return store_df


########
# PLOT #
########

plt.figure(figsize=(7,6))
store_df = reshape_average_guess('U100')
plt.axis([-.03,3.03,-0.0,1])
plt.xticks(range(len(store_df.index)), store_df.label)
plt.xlabel('$\mu(\pi_C,r)$: Posterior Induced by $\pi_C$ Conditional on $r$', fontsize = 15)
plt.ylabel('Frequency of $a=red$', fontsize = 15)
plt.yticks([0,.25,.5,.75,1]) 
plt.title("Receivers Responsiveness in $U100$ and $U100S$",  fontsize=15)

store_df = reshape_average_guess('U100')
plt.scatter(store_df.index, store_df.mean_dec_r, alpha = 1, marker = 's', color = 'gray', edgecolors = 'gray', linewidths = 2, label = '$U100$')
plt.plot(store_df.index, store_df.mean_dec_r, alpha = .2, color = 'gray', label='_nolegend_')

store_df = reshape_average_guess('U100S')
plt.scatter(store_df.index, store_df.mean_dec_r, alpha = 1, color = 'white', edgecolors = 'k', linewidths = 2, label = '$U100S$')
plt.plot(store_df.index, store_df.mean_dec_r, alpha = .2, color = 'k', label='_nolegend_')


plt.legend(loc = (0,.85), prop={'size': 15})
plt.text(0.1,-.33, '.', color='w', fontsize=14)
plt.show()

Replicate Figure C11

Probability of Guessing Red by Posterior and Message

Before running this code, run all the codes in the section titled "Replicate Figure 6"

In [17]:
import warnings
warnings.simplefilter('ignore')

# We silence the warnings. Warnings are produced because a few receivers always guess blue. 
# For them, the estimation procedure outputs a warning. 
# Despite the warning, however, the estimated coefficient is "correct" in that the estimated probability 
# that these receivers guess red for any posterior x is zero, as it should be. 

import statsmodels.discrete.discrete_model as sm
from scipy.stats import norm


rangex = np.linspace(0, 1, 50)
plt.figure(figsize = (8,6))

for treatment in ['U100S']:
    paystore = []
    paystore_pred = []
    
    for x in rangex: ## For loop a relevant class of strategies parameterized by x (see main draft)
        strategy = {'U100S' : [1, 0, 0, 1 - x, x, 0]} 
        DF = load_data(treatment, 16, 'R') ## Select the database of receivers
        DF = prepare_data(DF, '', '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank 
        df = reshape_data(DF)
        df['id'] = df.id.astype('int')

        phistore = []
        ESstore = []
        
        for id in df.id.unique(): ## For every receiver
            coeff = probit(verbose = 'No', id = id) ## Estimate probit coefficients for each receiver
            avg_response = {'red': yhat(posterior(strategy[treatment])[0], 'red', coeff), 
                            'blue': yhat(posterior(strategy[treatment])[1], 'blue', coeff),                 
                            'none': yhat(posterior(strategy[treatment])[2], 'none', coeff) }
            ES  = expectedpayoff(strategy, treatment, avg_response)
            ESstore = np.append(ESstore, ES)
        paystore = np.append(paystore, np.median(ESstore)) # Median
        
        ## Compute predictions: receiver's best respond to strategy(x); compute sender's expected payoff
        temp = np.array(posterior(strategy[treatment])) # Posteriors that strategy(x) induces
        ## receiver's best response, breaking ties in favor of sender
        avg_response_pred = {'red': (temp >=.5)[0], 
                                    'blue': (temp >=.5)[1],                 
                                    'none': (temp >=.5)[2]}     
        
        ## Next line computes sender's predicted expected payoff (2 dollars times probability of guessing red)
        payoff_pred = 2 * ((1/3 * strategy[treatment][0] + 2/3 * strategy[treatment][3]) * np.nan_to_num(avg_response_pred['red']) + 
                           (1/3 * strategy[treatment][1] + 2/3 * strategy[treatment][4])* np.nan_to_num(avg_response_pred['blue']) + 
                           (1/3 * strategy[treatment][2] + 2/3 * strategy[treatment][5])* np.nan_to_num(avg_response_pred['none'])) 
        
        # store payoff_pred
        paystore_pred = np.append(paystore_pred,  payoff_pred) 
    ## --> end of x loop <--
    
    
    ## Plot and format the looks:
    if treatment == "U100S":
        plt.plot(rangex, paystore[:] , c = 'black', marker='o',  markevery=3, markeredgecolor='black', markeredgewidth = 1, markerfacecolor='white', markersize=8,   linestyle = "dashed", alpha = 1, label =  "$" + treatment +  "$")
        plt.scatter(rangex, paystore_pred[:] , c = 'black',  s=13, alpha = .75, marker='o',label = "Predictions")
    plt.xlabel("$x$ -- Parametrized Sender's Strategy", fontsize = 15)
    plt.ylabel("Sender's Expected Payoff", fontsize = 15)
    plt.legend(loc = 2, prop = {'size' : 15})
    plt.axis([0,1,-0.01,1.35])
    plt.yticks([0,.25,.5,.75,1,1.25])
    plt.xticks([0,.25,.5,.75,1])
    
plt.show()

Replicate Figure D17

Receiver’s Response to Persuasive Messages: $\rho = 0.20$ vs. $\rho = 0.80$

In [18]:
from scipy.optimize import curve_fit

## Define polynomial function of degree 2, will be used to fit the data
def quadratic_function(x, a, b, c):
    return a + b * x + c * x ** 2

plt.figure(figsize=(15,6))

for rule in ['U', 'V']: # Compare Unverifiable and Verifiable information in two separate subplots
    plt.subplot(1,2, 1 if rule == 'U' else 2) 
    for treatment in [rule + '20', rule + '80']: # Compare treatments \rho=0.2 and \rho=1 in the same subplot
        msg = 'none' if rule == 'V' else 'red' # Choose the "persuasive" message (see main draft): red for U and none for V
        storem = []
        storeh = []
        storel = []
        DF = load_data(treatment, match = 16, role = 'S')
        DF = prepare_data(DF, 'session', 'Strong') 
        # The next few lines use the variables "post_red" and "post_none", which have been defined in "prepare_data" (see preamble)
        DF = DF[np.isnan(DF['post_' + msg]) == False] # Drop observations for which the persuasive msg has probability zero

        temp = DF.sort_values('post_' + msg, ascending = True) # Generate a temporary dataframe "temp" ordered by post_msg
        x = temp['post_' + msg].values # Generate a vector with post_msg 
        y = temp['m'+ msg[0] + 'dec']  # Generate a vector with receiver's guess to persuasive message 

        ## Estimate Quadratic Fit
        params, covar = curve_fit(quadratic_function, x, y, bounds = [0,1])

        ## Simulate 10'000 times to generate confidence bounds
        rangex = np.linspace(0,1,100) # Range of posterior beliefs 
        temp_par = np.random.multivariate_normal(params, covar, 10000) ## Generate 10'000 parameters from estimation above
        a, b, c = temp_par[:,0],  temp_par[:,1],  temp_par[:,2]
        for x in rangex:
            temp = quadratic_function(x, a, b, c) 
            tempstorem, tempstoreh, tempstorel = np.percentile(temp, 50), np.percentile(temp, 95), np.percentile(temp, 5)
            storeh = np.append(storeh, tempstoreh)
            storel = np.append(storel, tempstorel)
            storem = np.append(storem, tempstorem)
        
        ## Format the looks of the plot
        plt.plot(rangex, storem, '--' if treatment[1:]!='20' else '-', alpha =.7, c = 'black', linewidth = 2, label = '$' + treatment + "$")
        plt.plot(rangex, storel, '--' if treatment[1:]!='20' else '-', alpha =.35, c = 'black', linewidth = 1)
        plt.plot(rangex, storeh, '--' if treatment[1:]!='20' else '-', alpha =.35, c = 'black',  linewidth = 1)
        string = 'r' if rule == 'U' else 'n'
        plt.xlabel('Interim Posterior Conditional on $m=' + string +'$', fontsize = 15)
        plt.axis([0,1,0,1])
    plt.ylabel('Frequency of $a=red$', fontsize = 15)
    plt.title('Unverifiable Information' if treatment[0] == 'U' else 'Verifiable Information', fontsize = 15)
    plt.legend(loc = 2, fontsize = 14)
plt.show()

Replicate Figure D18

k-Means – Representative Strategies in Treatments with Full Commitment

In [19]:
n = 4

## Figure in the Left panel
np.random.seed(0)
treatment = 'V100'
DF = load_data(treatment, match = 16, role = 'S')
DF = prepare_data(DF, level = '', method = '') # imputation method is irrelevant since this is is a full-commitment treatment, left blank 
DF, AS = clusters_full_commitment(DF, n) 
DF["corr_agg"] = correlation_bayesian_receiver(DF, 'agg')
df = DF
AS = AS/100
relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,1))

colz = ['b','r', 'orchid','orange', 'orange', 'pink']
marz = ['s','o','*',"^","p","o"]
lab = relsize

fig = plt.figure(figsize=(7,6))
plt.scatter(0.01, 1/2, c = 'white', s = 150, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1, label = "Prediction, $\phi^B = 0.79$")
for i in [2,0, 1, 3]:
    plt.scatter(AS[i,0], AS[i,4], c = colz[i], s = 120 if i!=0 else 200, alpha = .75, edgecolors=  colz[i], marker = marz[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
    plt.xlabel('$\pi_C(r|R)$', fontsize = 15)
    plt.ylabel('$\pi_C(b|B)$', fontsize = 15)
    plt.axis([-.01, 1.01, -.01, 1.01])
plt.xticks([0, .25, .50, .75,1.00])
plt.yticks([0, .25, .50, .75,1.00])
plt.title("Treatment $" + treatment +"$", fontsize = 15)

plt.legend(loc = (0.05,-.3), fontsize = 12, ncol=2)
plt.show()


## Figure in the Right panel
np.random.seed(0)
treatment = 'U100'
DF = load_data(treatment, match = 16, role = 'S')
DF = prepare_data(DF, level = '', method = '')  # imputation method is irrelevant since this is is a full-commitment treatment, left blank 
DF, AS = clusters_full_commitment(DF, n) 
DF["corr_agg"] = correlation_bayesian_receiver(DF, 'agg')
df = DF
AS = AS/100
relsize = (np.round((100*df.cluster.value_counts(sort = False)/df.cluster.size).values,1))

colz = ['b','orange', 'r','g', 'orange', 'pink']
marz = ['s','^','o',"D","p","o"]
lab = relsize

fig = plt.figure(figsize=(7,6))
plt.scatter(.99, 1/2, c = 'white', s = 150, alpha = .75, edgecolors =  'black', marker = 'X', linewidth = 1, label = "Prediction, $\phi^B = 0.79$")
for i in [0, 1, 2, 3]:
    plt.scatter(AS[i,0], AS[i,4], c = colz[i], s = 120, alpha = .75, edgecolors=  colz[i], marker = marz[i], linewidth = 2, label = "Freq " + str(relsize[i]) +"\%, $\phi^B = $ " + str(np.round(df[df.cluster==i].corr_agg.mean(),2)))
    plt.xlabel('$\pi_C(r|R)$', fontsize = 15)
    plt.ylabel('$\pi_C(b|B)$', fontsize = 15)
    plt.axis([-.01, 1.01, -.01, 1.01])
plt.xticks([0, .25, .50, .75,1.00])
plt.yticks([0, .25, .50, .75,1.00])
plt.title("Treatment $" + treatment +"$", fontsize = 15)
plt.legend(loc = (0.05,-.3), fontsize = 12, ncol=2)
plt.show()

Replicate Table D8

In [20]:
for treatment in ["U20", "V20"]:
    DF = load_data(treatment, match = 16, role = 'S')
    DF = prepare_data(DF, level = 'session', method = 'Strong') 
    print(treatment)
    print(np.round(DF.revrred.mean()/100,2))
    print(np.round(DF.revrblue.mean()/100,2))
    print(np.round(DF.revrnone.mean()/100,2))
    print(np.round(DF.revbred.mean()/100,2))
    print(np.round(DF.revbblue.mean()/100,2))
    print(np.round(DF.revbnone.mean()/100,2))
    print("")
U20
0.88
0.06
0.05
0.65
0.24
0.12

V20
0.92
0.0
0.08
0.0
0.27
0.73

Replicate Table D9

In [21]:
pd.set_option('mode.chained_assignment', None)
for treatment in ["U20", "U100"]:
    DF = load_data(treatment, match = 16, role = 'S')
    DF = prepare_data(DF, 'subject', 'Weak')
    ## Focus attention on a subset of strategies as explained in Section D.6
    df = DF[(DF.rred >= 95) & (DF.bblue>= 95)] 
    stage = "agg"
    ## Compute matrix P
    df["thetaR_aR"] = 1 / (3 * 100) * (df['{}rred'.format(stage)] * (df['mrdec']) + df['{}rblue'.format(stage)]  * (df['mbdec']) + df['{}rnone'.format(stage)] * (df['mndec']))
    df["thetaR_aB"] = 1 / (3 * 100) * (df['{}rred'.format(stage)] * (1 - df['mrdec']) + df['{}rblue'.format(stage)]  * (1 - df['mbdec']) + df['{}rnone'.format(stage)] * (1 - df['mndec']))
    df["thetaB_aB"] = 2 / (3 * 100) * (df['{}bred'.format(stage)] * (1 - df['mrdec']) + df['{}bblue'.format(stage)]  * (1 - df['mbdec']) + df['{}bnone'.format(stage)] * (1 - df['mndec']))
    df["thetaB_aR"] = 2 / (3 * 100) * (df['{}bred'.format(stage)] * (df['mrdec']) + df['{}bblue'.format(stage)]  * (df['mbdec']) + df['{}bnone'.format(stage)] * (df['mndec']))         
    print(treatment)
    print(np.round(df.thetaR_aR.mean(),2), np.round(df.thetaR_aB.mean(),2))
    print(np.round(df.thetaB_aR.mean(),2), np.round(df.thetaB_aB.mean(),2))
    print("")
U20
0.13 0.2
0.13 0.54

U100
0.25 0.08
0.04 0.63

Earnings

These numbers include the show up fee of 10 dollars.

In [22]:
#### print("In reference to Section 3.1")
DF = pd.read_stata(r'data.dta')
DF = DF[DF.match == 25]
DF = DF[(DF.u100s == 0) & (DF.u100h == 0)]
DF.wealth.mean()
print('Average = ' + str(10 + np.round(DF.wealth.mean(),2)))
print('Max = ' + str(10 + np.max(DF.wealth)))
print('Min = ' + str(10 + np.min(DF.wealth)))
print("")

print("In reference to Section B.1")
DF = pd.read_stata(r'data.dta')
DF = DF[DF.match == 25]
DF = DF[(DF.u100h == 1)]
DF.wealth.mean()
print('Average = ' + str(10 + np.round(DF.wealth.mean(),2)))
print('Max = ' + str(10 + np.max(DF.wealth)))
print('Min = ' + str(10 + np.min(DF.wealth)))
print("")

print("In reference to Section B.2")
DF = pd.read_stata(r'data.dta')
DF = DF[DF.match == 25]
DF = DF[(DF.u100s == 1)]
DF.wealth.mean()
print('Average = ' + str(10 + np.round(DF.wealth.mean(),2)))
print('Max = ' + str(10 + np.max(DF.wealth)))
print('Min = ' + str(10 + np.min(DF.wealth)))
Average = 36.56
Max = 60.0
Min = 12.0

In reference to Section B.1
Average = 31.84
Max = 48.0
Min = 13.0

In reference to Section B.2
Average = 34.09
Max = 52.0
Min = 14.0