IPython notebook to analyze Pew Research Center's attitudes to Russia survey, March 2014, using ternary plots

David Taylor, www.prooffreader.com

In [3]:
import pandas as pd
from six.moves import cStringIO as cStringIO

# Note that column names begin with a digit, so they can only be called dict-like, not as attributes
# e.g. df['2014fav']

def terncol(xval, yval):
    """returns rgb tuple from ternary x and y coordinates where upper left = blue, 
    upper middle = grey, upper right = orange, lower = white"""
    import math
    # note some of these will be visually imperceptibly off by 0.5 because I was too lazy to properly round
    blue = 0.0
    red =    0 + xval * (225-0)
    green =  0 + xval * (127-0)  
    blue =   255 + xval * (0-255)
    red = red + ((1-yval)/.54) * (255-red)
    green = green + ((1-yval)/.54) * (255-green)
    blue = blue + ((1-yval)/.54) * (255-blue)
    return '#%02x%02x%02x' % (red, green, blue), int(red), int(green), int(blue)

colordict = {"Russia":"#ee0000",
             "USA": "#3bcddb",
             "Europe":"#1111ee",
             "Latin America":"#39b54a",
             "Asia":"#c23cd1",
             "Middle East":"#f7941d",
             "Africa":"#000000"}

colorlist = ['Russia', 'USA', 'Europe', 'Asia', 'Latin America', 'Middle East', 'Africa']

data = """
country,region,2013fav,2013unfav,2014fav,2014unfav
Argentina,Latin America,26,29,19,37
Bangladesh,Asia,,,60,33
Brazil,Latin America,34,52,24,59
Chile,Latin America,39,38,34,45
China,Asia,49,39,66,23
Colombia,Latin America,,,24,37
Egypt,Africa,30,64,24,71
El Salvador,Latin America,27,29,23,36
France,Europe,36,64,26,73
Germany,Europe,32,60,19,79
Ghana,Africa,49,26,42,31
Greece,Europe,63,33,61,35
India,Asia,45,23,39,16
Indonesia,Asia,43,33,38,43
Israel,Middle East,21,77,30,68
Italy,Europe,31,56,20,74
Japan,Asia,27,64,23,69
Jordan,Middle East,25,70,22,75
Kenya,Africa,47,27,49,32
Lebanon,Middle East,46,53,45,54
Malaysia,Asia,47,22,34,38
Mexico,Latin America,28,38,21,44
Nicaragua,Latin America,,,45,27
Nigeria,Africa,38,30,41,27
Pakistan,Asia,19,32,11,29
Palestine,Middle East,29,57,41,46
Peru,Latin America,,,34,35
Philippines,Asia,35,52,46,43
Poland,Europe,36,54,12,81
Russia,Russia,83,14,92,6
Senegal,Africa,42,21,39,30
South Africa,Africa,26,53,25,51
South Korea,Asia,53,33,43,48
Spain,Europe,38,51,18,74
Tanzania,Africa,,,49,25
Thailand,Asia,,,48,29
Tunisia,Africa,35,37,35,38
Turkey,Middle East,19,66,16,73
Uganda,Africa,28,22,34,31
United Kingdom,Europe,38,39,25,63
Ukraine,Europe,,,35,60
United States,USA,37,43,19,72
Venezuela,Latin America,40,41,36,51
Vietnam,Asia,,,75,14
"""

df = pd.read_csv(cStringIO(data), skiprows=1, skipinitialspace=True)

df['2013noans'] = 100 - df['2013fav'] - df['2013unfav']
df['2014noans'] = 100 - df['2014fav'] - df['2014unfav']

from math import sqrt as sqrt
df['2014x'] = (2*df['2014fav']+df['2014noans'])/(2*(df['2014fav']+df['2014unfav']+df['2014noans']))
df['2014y'] = 1 - (sqrt(3)*df['2014noans'])/((2*(df['2014fav']+df['2014unfav']+df['2014noans'])))
df['2013x'] = (2*df['2013fav']+df['2013noans'])/(2*(df['2013fav']+df['2013unfav']+df['2013noans']))
df['2013y'] = 1 - (sqrt(3)*df['2013noans'])/((2*(df['2013fav']+df['2013unfav']+df['2013noans'])))


from math import log
df['2014log2favdivunfav'] = 9999
df['2014pluralitytxt'] = ''
df['2014plurality012'] = -1
df['color'] = ''

for i in range(len(df)):
    df['color'].iloc[i] = terncol(df['2014x'].iloc[i],df['2014y'].iloc[i])[0]
    df['2014log2favdivunfav'].iloc[i] = log(1.0*df['2014fav'].iloc[i]/df['2014unfav'].iloc[i],2)
    if df['2014unfav'].iloc[i] < df['2014fav'].iloc[i] and df['2014noans'].iloc[i] <= df['2014fav'].iloc[i]:
        df['2014pluralitytxt'].iloc[i] = 'F'
        df['2014plurality012'].iloc[i] = 2
    elif df['2014noans'].iloc[i] <= df['2014unfav'].iloc[i]:
        df['2014pluralitytxt'].iloc[i] = 'U'
        df['2014plurality012'].iloc[i] = 0
    else:
        df['2014pluralitytxt'].iloc[i] = 'N'
        df['2014plurality012'].iloc[i] = 1

df['2014fav_rank'] = df['2014fav'].rank(ascending=False)
df['2014unfav_rank'] = df['2014unfav'].rank()
df['2014ratio_rank'] = df['2014log2favdivunfav'].rank(ascending=False)        
        
df['2014rankstdev'] = 0.0
import numpy as np
for i in range(len(df)):   
    ranks = []
    ranks.append(df['2014fav_rank'].iloc[i])
    ranks.append(df['2014unfav_rank'].iloc[i])
    ranks.append(df['2014ratio_rank'].iloc[i])
    df['2014rankstdev'].iloc[i] = np.std(ranks, ddof=0) 
      
country_codes = {'Argentina':'ARG',
'Brazil':'BRA',
'Chile':'CHL',
'China':'CHN',
'Egypt':'EGY',
'El Salvador':'SLV',
'France':'FRA',
'Germany':'DEU',
'Ghana':'GHA',
'Greece':'GRC',
'India':'IND',
'Indonesia':'IDN',
'Israel':'ISR',
'Italy':'ITA',
'Japan':'JPN',
'Jordan':'JOR',
'Kenya':'KEN',
'Lebanon':'LBN',
'Malaysia':'MYS',
'Mexico':'MEX',
'Nigeria':'NGA',
'Pakistan':'PAK',
'Palestine':'PSE',
'Philippines':'PHL',
'Poland':'POL',
'Russia':'RUS',
'Senegal':'SEN',
'South Africa':'ZAF',
'South Korea':'KOR',
'Spain':'ESP',
'Tunisia':'TUN',
'Turkey':'TUR',
'Uganda':'UGA',
'United Kingdom':'GBR',
'United States':'USA',
'Venezuela':'VEN'}

df['code'] = ''
for i in range(len(df)):
    if df['country'].iloc[i] in country_codes.keys():
        df['code'].iloc[i] = country_codes[df['country'].iloc[i]]
        
df['2013xanim'] = df['2013x']
df['2013yanim'] = df['2013y']
df['2014xanim'] = df['2014x']
df['2014yanim'] = df['2014y']

# adjustments to prevent overlapping in first and last frame of animation

df['2013yanim'][df[df.code == 'SLV'].index] +=0.01
df['2013yanim'][df[df.code == 'ARG'].index] -=0.005
df['2013yanim'][df[df.code == 'MYS'].index] +=0.01
df['2013yanim'][df[df.code == 'IND'].index] -=0.005
df['2013xanim'][df[df.code == 'GBR'].index] -=0.015 
df['2013yanim'][df[df.code == 'GBR'].index] -=0.01
df['2013yanim'][df[df.code == 'CHL'].index] +=0.01
df['2013yanim'][df[df.code == 'GHA'].index] +=0.01
df['2013yanim'][df[df.code == 'KEN'].index] -=0.005
df['2013xanim'][df[df.code == 'PSE'].index] -=0.005 
df['2013yanim'][df[df.code == 'PSE'].index] -=0.005
df['2013xanim'][df[df.code == 'VEN'].index] +=0.005
df['2013xanim'][df[df.code == 'USA'].index] -=0.01
df['2013xanim'][df[df.code == 'ESP'].index] +=0.01
df['2013yanim'][df[df.code == 'ITA'].index] +=0.005
df['2013yanim'][df[df.code == 'BRA'].index] -=0.015
df['2013yanim'][df[df.code == 'CHL'].index] +=0.005
df['2013xanim'][df[df.code == 'POL'].index] -=0.005

df['2014xanim'][df[df.code == 'SEN'].index] -=0.005
df['2014yanim'][df[df.code == 'SEN'].index] +=0.005
df['2014xanim'][df[df.code == 'NGA'].index] +=0.005
df['2014yanim'][df[df.code == 'NGA'].index] -=0.005
df['2014yanim'][df[df.code == 'TUN'].index] +=0.005
df['2014yanim'][df[df.code == 'MYS'].index] -=0.005
df['2014xanim'][df[df.code == 'DEU'].index] -=0.005
df['2014yanim'][df[df.code == 'ITA'].index] +=0.005
df['2014yanim'][df[df.code == 'TUR'].index] -=0.01
df['2014yanim'][df[df.code == 'USA'].index] -=0.01

# calculate animation steps, x and y positions between 2013 and 2014
    
xsteps = ['xs01', 'xs02', 'xs03', 'xs04', 'xs05', 'xs06', 'xs07', 'xs08', 'xs09', 'xs10', 'xs11', 'xs12', 'xs13', 'xs14', 'xs15', 'xs16', 'xs17', 'xs18', 'xs19']
ysteps = ['ys01', 'ys02', 'ys03', 'ys04', 'ys05', 'ys06', 'ys07', 'ys08', 'ys09', 'ys10', 'ys11', 'ys12', 'ys13', 'ys14', 'ys15', 'ys16', 'ys17', 'ys18', 'ys19']

for i in range(len(xsteps)):
    df[xsteps[i]] = -9999
    df[ysteps[i]] = -9999
    for country in list(df['country']):
        x2013 = df[df.country == country]['2013xanim'].iloc[0]
        y2013 = df[df.country == country]['2013yanim'].iloc[0]
        x2014 = df[df.country == country]['2014xanim'].iloc[0]
        y2014 = df[df.country == country]['2014yanim'].iloc[0]
        xcol = xsteps[i]
        ycol = ysteps[i]
        thisx = x2013 + ((i+1)*1.0/20)*(x2014-x2013)
        thisy = y2013 + ((i+1)*1.0/20)*(y2014-y2013)
        df[xcol].ix[df[df['country']==country].index] = thisx
        df[ycol].ix[df[df['country']==country].index] = thisy
        
anim_legend1 = ['ARG: Argentina', 'BRA: Brazil', 'CHL: Chile', 'CHN: China', 'DEU: Germany', 'EGY: Egypt', 
               'ESP: Spain', 'FRA: France', 'GBR: United Kingdom', 'GHA: Ghana', 'GRC: Greece', 'IDN: Indonesia', 
               'IND: India', 'ISR: Israel', 'ITA: Italy', 'JOR: Jordan', 'JPN: Japan', 'KEN: Kenya']
anim_legend2 = ['KOR: South Korea', 'LBN: Lebanon', 'MEX: Mexico', 'MYS: Malaysia', 'NGA: Nigeria', 
               'PAK: Pakistan', 'PHL: Philippines', 'POL: Poland', 'PSE: Palestine', 'RUS: Russia', 
               'SEN: Senegal', 'SLV: El Salvador', 'TUN: Tunisia', 'TUR: Turkey', 'UGA: Uganda', 
               'USA: United States', 'VEN: Venezuela', 'ZAF: South Africa']
In [5]:
print df.iloc[0]
country                    Argentina
region                 Latin America
2013fav                           26
2013unfav                         29
2014fav                           19
2014unfav                         37
2013noans                         45
2014noans                         44
2014x                           0.41
2014y                      0.6189488
2013x                          0.485
2013y                      0.6102886
2014log2favdivunfav       -0.9615259
2014pluralitytxt                   N
2014plurality012                   1
...
ys12    0.6134847
xs13      0.43625
ys13    0.6141677
xs14       0.4325
ys14    0.6148507
xs15      0.42875
ys15    0.6155338
xs16        0.425
ys16    0.6162168
xs17      0.42125
ys17    0.6168998
xs18       0.4175
ys18    0.6175828
xs19      0.41375
ys19    0.6182658
Name: 0, Length: 63, dtype: object
In [5]:
print df.columns
Index([u'country', u'region', u'2013fav', u'2013unfav', u'2014fav', u'2014unfav', u'2013noans', u'2014noans', u'2014x', u'2014y', u'2013x', u'2013y', u'2014log2favdivunfav', u'2014pluralitytxt', u'2014plurality012', u'color', u'2014fav_rank', u'2014unfav_rank', u'2014ratio_rank', u'2014rankstdev', u'code', u'2013xanim', u'2013yanim', u'2014xanim', u'2014yanim', u'xs0', u'ys0', u'xs1', u'ys1', u'xs2', u'ys2', u'xs3', u'ys3', u'xs4', u'ys4', u'xs5', u'ys5', u'xs6', u'ys6', u'xs7', u'ys7', u'xs8', u'ys8', u'xs9', u'ys9'], dtype='object')

Ternary plot with coloured fills according to continent/area

In [7]:
import bokeh.plotting as bplt
from bokeh.objects import HoverTool, ColumnDataSource, Range1d
from collections import OrderedDict
bplt.output_file('ternary_russia.html', mode="cdn")

source = ColumnDataSource(
    data=dict(
        country=df['country'],
        favorable=[str(x)+"%" for x in df['2014fav']],
        unfavorable=[str(x)+"%" for x in df['2014unfav']],
        noanswer=[str(x)+"%" for x in df['2014noans']],
        regcolor=[colordict[x] for x in df['region']]
    )
)

bplt.figure(plot_width=640, plot_height=600, tools="hover", x_axis_type=None, y_axis_type=None)
bplt.hold()

bplt.circle(df['2014x'], df['2014y'], source=source, color="regcolor", 
       line_color='#333333', size = 11, title='')

bplt.line(x=[0,0.5],y=[1,0.134])
bplt.line(x=[0.5,1],y=[0.134,1])
bplt.line(x=[1,0],y=[1,1])

bplt.text([0.5], [1.06], text="Ternary plot of countries' attitudes toward Russia in March 2014",
          text_align="center", text_baseline="bottom", angle=0, 
          text_font="palatino", text_font_style="bold", text_font_size="14.5pt", text_color="#222222")
bplt.text([0.5], [1.02], text="Mouseover for detailed country data", text_align="center", text_baseline="bottom", 
          angle=0, text_font_style="italic", text_font = "palatino", text_font_size="10pt", text_color="#cc2222")
bplt.text([-0.01], [1.0], text="100% FAVORABLE", text_align="right", text_baseline="bottom", angle=(3.1415/2), 
          text_font="franklin gothic demi cond", text_font_size="15pt")
bplt.text([1.01], [1], text="100% UNFAVORABLE", text_align="left", text_baseline="bottom", angle=(-3.1415/2), 
          text_font="franklin gothic demi cond", text_font_size="15pt")
bplt.text([0.5], [0.128], text="100% NO ANSWER", text_align="center", text_baseline="top", angle=0, 
          text_font="franklin gothic demi cond", text_font_size="14pt")
bplt.text([-0.07], [0.09], text="data: Pew Research Center", text_align="left", text_baseline="top", angle=0, 
          text_font="palatino", text_font_size = "11pt", text_font_style="italic", text_font_color="#cc0000")
bplt.text([1.06], [0.09], text="www.prooffreader.com", text_align="right", text_baseline="top", angle=0, 
          text_font="palatino", text_font_size = "11pt", text_font_style="italic", text_font_color="#cc0000")



bplt.curplot().x_range = Range1d(start=-0.1, end=1.1)
bplt.curplot().y_range = Range1d(start=0.05, end=1.12)

hover = [t for t in bplt.curplot().tools if isinstance(t, HoverTool)][0]
hover.tooltips = OrderedDict([
    ('Country', "@country"),
    ('Favorable', "@favorable"),
    ('Unfavorable', "@unfavorable"),
    ('No answer', "@noanswer")
])

#legend
ystart = 0.37
yint = -0.03
xstart = 0.85
xoff = 0.02
for i in range(len(colorlist)):
    bplt.text([xstart+xoff], [ystart+i*yint], text=colorlist[i], text_align="left", text_baseline="middle", angle=0, text_font_size="9pt")
    bplt.circle([xstart], [ystart+i*yint], color=colordict[colorlist[i]], 
                line_color='#333333', size = 10)


bplt.show()
bplt.reset_output()
Session output file 'ternary_russia.html' already exists, will be overwritten.

Ternary plot with color-mapped fills according to ternary points

In [20]:
import bokeh.plotting as bplt
from bokeh.objects import HoverTool, ColumnDataSource, Range1d
from collections import OrderedDict
bplt.output_file('ternary_russia.html', mode="cdn")

source = ColumnDataSource(
    data=dict(
        country=df['country'],
        favorable=[str(x)+"%" for x in df['2014fav']],
        unfavorable=[str(x)+"%" for x in df['2014unfav']],
        noanswer=[str(x)+"%" for x in df['2014noans']],
        regcolor=df['color']
    )
)

bplt.figure(plot_width=640, plot_height=600, tools="hover", x_axis_type=None, y_axis_type=None)
bplt.hold()

bplt.circle(df['2014x'], df['2014y'], source=source, color="regcolor", 
       size = 15, title='')

bplt.line(x=[0,0.5],y=[1,0.134])
bplt.line(x=[0.5,1],y=[0.134,1])
bplt.line(x=[1,0],y=[1,1])

bplt.text([0.5], [1.06], text="Ternary plot of countries' attitudes toward Russia in March 2014",
          text_align="center", text_baseline="bottom", angle=0, 
          text_font="palatino", text_font_style="bold", text_font_size="14.5pt", text_color="#222222")
bplt.text([0.5], [1.02], text="Mouseover for detailed country data", text_align="center", text_baseline="bottom", 
          angle=0, text_font_style="italic", text_font = "palatino", text_font_size="10pt", text_color="#cc2222")
bplt.text([-0.01], [1.0], text="100% FAVORABLE", text_align="right", text_baseline="bottom", angle=(3.1415/2), 
          text_font="franklin gothic demi cond", text_font_size="15pt")
bplt.text([1.01], [1], text="100% UNFAVORABLE", text_align="left", text_baseline="bottom", angle=(-3.1415/2), 
          text_font="franklin gothic demi cond", text_font_size="15pt")
bplt.text([0.5], [0.128], text="100% NO ANSWER", text_align="center", text_baseline="top", angle=0, 
          text_font="franklin gothic demi cond", text_font_size="14pt")
bplt.text([-0.07], [0.09], text="data: Pew Research Center", text_align="left", text_baseline="top", angle=0, 
          text_font="palatino", text_font_size = "11pt", text_font_style="italic", text_font_color="#cc0000")
bplt.text([1.06], [0.09], text="www.prooffreader.com", text_align="right", text_baseline="top", angle=0, 
          text_font="palatino", text_font_size = "11pt", text_font_style="italic", text_font_color="#cc0000")

bplt.curplot().x_range = Range1d(start=-0.1, end=1.1)
bplt.curplot().y_range = Range1d(start=0.05, end=1.12)

hover = [t for t in bplt.curplot().tools if isinstance(t, HoverTool)][0]
hover.tooltips = OrderedDict([
    ('Country', "@country"),
    ('Favorable', "@favorable"),
    ('Unfavorable', "@unfavorable"),
    ('No answer', "@noanswer")
])

bplt.show()
bplt.reset_output()
Session output file 'ternary_russia.html' already exists, will be overwritten.

Ladder Diagram of ranks

In [81]:
import bokeh.plotting as bplt
from bokeh.objects import HoverTool, ColumnDataSource, Range1d
from collections import OrderedDict
bplt.output_file('ladder_diagram.html')

bplt.figure(plot_width=1200, plot_height=1300, tools="hover", x_axis_type=None, y_axis_type=None,
            title = "Comparison of ranking metrics in countries' attitudes toward Russia, March 2014")
bplt.hold()

xvals = [0,1,2]
cnames = []
for i in range(3):
    cnames.append([])
df.sort('2014unfav', ascending=False, inplace=True)
df.sort('2014fav', ascending=True, inplace=True)
for i in range(len(df)):
    cnames[0].append(df['country'].iloc[i])
df.sort('2014unfav', ascending=False, inplace=True)
for i in range(len(df)):
    cnames[1].append(df['country'].iloc[i])
df.sort('2014log2favdivunfav', ascending=True, inplace=True)
for i in range(len(df)):
    cnames[2].append(df['country'].iloc[i])

for x in range(3):
    for y in range(len(df)):
        bplt.text([x], [y], text=cnames[x][y], text_align="center", text_baseline="middle", angle=0, text_font_size="12pt")

bplt.text([0], [len(df)+3], text="Most", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold")
bplt.text([0], [len(df)+2], text="Favorable", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold") 
bplt.text([1], [len(df)+3], text="Least", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold") 
bplt.text([1], [len(df)+2], text="Unfavorable", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold") 
bplt.text([2], [len(df)+3], text="Highest ratio", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold") 
bplt.text([2], [len(df)+2], text="Favorable:Unfavorable", text_align="center", text_baseline="bottom", 
          angle=0, text_font_size="14pt", text_font_style="bold") 

cutoff = 12
for country in list(df['country']):
    yvals = []
    for i in range(3):
        yvals.append(cnames[i].index(country))
    for i in range(2):
        j = i + 1
        if abs(yvals[j] - yvals[i]) < cutoff:
            bplt.line(x=[i,j], y=[yvals[i], yvals[j]], alpha=0.3)
        else:
            bplt.line(x=[i,j], y=[yvals[i], yvals[j]], alpha=0.4, color='red')

bplt.curplot().x_range = Range1d(start=-1, end=4)
    
bplt.show()
bplt.reset_output()
Session output file 'ladder_diagram.html' already exists, will be overwritten.

Scatter plot of % no answer vs. standard deviation of ranks in three metrics

In [19]:
import bokeh.plotting as bplt
from bokeh.objects import HoverTool, ColumnDataSource, Range1d
from collections import OrderedDict
bplt.output_file('stdev_plot.html', mode="cdn")

source = ColumnDataSource(
    data=dict(
        country=df['country'],
        favorable=[str(x)+"%" for x in df['2014fav']],
        unfavorable=[str(x)+"%" for x in df['2014unfav']],
        noanswer=[str(x)+"%" for x in df['2014noans']],
        regcolor=[colordict[x] for x in df['region']]
    )
)

bplt.figure(plot_width=600, plot_height=400, tools="hover", title="")
bplt.hold()

bplt.circle(df['2014rankstdev'], df['2014noans'], source=source, color="regcolor", 
       line_color='#333333', size = 14)

#bplt.curplot().x_range = Range1d(start=-0.3, end=1.3)

hover = [t for t in bplt.curplot().tools if isinstance(t, HoverTool)][0]
hover.tooltips = OrderedDict([
    ('Country', "@country"),
    ('Favorable', "@favorable"),
    ('Unfavorable', "@unfavorable"),
    ('No answer', "@noanswer")
])

#legend
leg = colordict.keys()
ystart = 22
yint = -3
xstart = 11.5
xoff = .3
for i in range(len(leg)):
    key = leg[i]
    bplt.text([xstart+xoff], [ystart+i*yint], text=key, text_align="left", text_baseline="middle", angle=0, text_font_size="9pt")
    bplt.circle([xstart], [ystart+i*yint], color=colordict[key], 
                line_color='#333333', size = 10)

    
bplt.oval([7.5], [42.3], [22.5], [14.5], angle=0.36, color=None, line_color='#cc0000')
bplt.oval([4.2], [4.2], [14], [26], angle=-0.4, color=None, line_color='#cc0000')

bplt.xaxis().axis_label='Standard deviation of 3 ranks'
bplt.yaxis().axis_label='% No Answer'

bplt.show()
bplt.reset_output()
Session output file 'stdev_plot.html' already exists, will be overwritten.

Ternary plots for 2013-2014 animation

In [6]:
import bokeh.plotting as bplt
from bokeh.objects import Range1d
from collections import OrderedDict
bplt.output_file('animation_2013_2014.html')

xcols = ['2013xanim', 'xs01', 'xs02', 'xs03', 'xs04', 'xs05', 'xs06', 'xs07', 'xs08', 'xs09', 'xs10', 
          'xs11', 'xs12', 'xs13', 'xs14', 'xs15', 'xs16', 'xs17', 'xs18', 'xs19', '2014xanim']
ycols = ['2013yanim', 'ys01', 'ys02', 'ys03', 'ys04', 'ys05', 'ys06', 'ys07', 'ys08', 'ys09', 'ys10', 
          'ys11', 'ys12', 'ys13', 'ys14', 'ys15', 'ys16', 'ys17', 'ys18', 'ys19', '2014yanim']


countries = list(df[df['2013xanim'] > 0].country)
codes = list(df[df['2013xanim'] > 0].code)

for i in range(len(xcols)):
    bplt.figure(plot_width=1000, plot_height=650, tools="hover", x_axis_type=None, y_axis_type=None)
    bplt.hold()

    for j in range(len(countries)):
        xpos = df[df.country==countries[j]][xcols[i]].iloc[0]
        ypos = df[df.country==countries[j]][ycols[i]].iloc[0]
        colr = colordict[df[df.country==countries[j]]['region'].iloc[0]]
        code = codes[j]
        bplt.text([xpos], [ypos], text=code, text_align="center", text_baseline="top", angle=0, text_font_size = "12pt",
                  text_font="franklin gothic demi cond", text_color=colr)
        
    bplt.line(x=[0,0.5],y=[1,0.134])
    bplt.line(x=[0.5,1],y=[0.134,1])
    bplt.line(x=[1,0],y=[1,1])

    bplt.text([-0.02], [1], text="Unfavorable", text_align="right", text_baseline="middle", angle=0, text_font="franklin gothic demi cond", text_font_size="14pt")
    bplt.text([1.02], [1], text="Favorable", text_align="left", text_baseline="middle", angle=0, text_font="franklin gothic demi cond", text_font_size="14pt")
    bplt.text([0.5], [0.127], text="No answer", text_align="center", text_baseline="top", angle=0, text_font="franklin gothic demi cond", text_font_size="14pt")
    bplt.text([0], [0.134], text="www.prooffreader.com", text_align="right", text_baseline="top", angle=0, text_font="palatino", text_font_size = "10pt")

    bplt.curplot().x_range = Range1d(start=-0.3, end=1.3)

    #legend
    ystart = 0.6
    yint = -0.03
    xstart = 0.8
    for i in range(18):
        key = anim_legend1[i]
        bplt.text([xstart], [ystart+i*yint], text=key, text_align="left", text_baseline="middle", angle=0, text_font_size="9pt")
    for i in range(18):
        key = anim_legend2[i]
        bplt.text([xstart+0.25], [ystart+i*yint], text=key, text_align="left", text_baseline="middle", angle=0, text_font_size="9pt")


bplt.show()
bplt.reset_output()
Session output file 'animation_2013_2014.html' already exists, will be overwritten.