First check which Plotly version is installed on your machine:
import plotly
plotly.__version__
'1.0.19'
If not version 1.0.19 or up, please upgrade using pip:
$ pip install plotly --upgrade
or
$ sudo pip install plotly --upgrade
Now, import the plotly
and tools
module.
import plotly.plotly as py # (New syntax!) tools to communicate with Plotly's server
import plotly.tools as tls # (NEW!) useful Python/Plotly tools
If you have already set up a credentials file, you are now signed in to Plotly and ready to communicate with Plotly's servers. See the Plotly's Python API User Guide for more info.
If you don't have a credentials file set up on your machine, run in Python/IPython:
>>> py.sign_in('<your-username>','<your-api-key>')
Now, let's import the graph objects needed for this notebook. First, the basic ones:
from plotly.graph_objs import Figure, Data, Layout
Scatter and objects to set up style
from plotly.graph_objs import Scatter, Marker, Line, XAxis, YAxis, Font
and finally the objects corresponding to X and Y error bars:
from plotly.graph_objs import ErrorY, ErrorX
Let's attempt to replicate this Nature graph from Paul N. Pearson and Martin R. Palmer (2000) in Plotly. The graph was converted to data using Engauge Digitizer.
We first import the data from the csv files (available in this github repo:)
import numpy as np
import csv
# Define a csv column reader function
def get_csv_col(filepath,col_id):
'''
Read column of csv file, return a Numpy array where
each entry corresp. to a particular student
1) filepath:
1) col_id: id of column requested, found in first row (a string)
'''
with open(filepath,'r') as data_file:
reader = csv.reader(data_file) # define reader object
the_col = reader.next().index(col_id) # look for 'col_id' row 1 then skip row
# Retrieve all entry of column 'the_col', put in Numpy array
return np.array([float(row[the_col]) for row in reader])
# -- note that the 'with' statement automatically closes
# 'filepath' at the end of its block of code
time = get_csv_col('co2-co2.csv','x')
co2 = get_csv_col('co2-co2.csv','Curve1')
co2_high = get_csv_col('co2-errory-high.csv','Curve1')
co2_low = get_csv_col('co2-errory-low.csv','Curve1')
time_left = get_csv_col('co2-errorx-left.csv','x')
time_right = get_csv_col('co2-errorx-right.csv','x')
Compute the errors (or uncertainties) in the $\text{CO}_2$ and time measurements with the high and low values retrieved from the csv files:
err_co2_high = co2_high - co2
err_co2_low = co2 - co2_low
err_time_left = time - time_left
err_time_right = time_right - time
To see the syntax and the available options in ErrorY
and ErrorX
, run help()
:
help(ErrorY) # help(ErrorX) is indentical
Help on class ErrorY in module plotly.graph_objs.graph_objs: class ErrorY(PlotlyDict) | A dictionary-like object for representing a set of errory bars in plotly. | | | Quick method reference: | | ErrorY.update(changes) | ErrorY.strip_style() | ErrorY.get_data() | ErrorY.to_graph_objs() | ErrorY.validate() | ErrorY.to_string() | ErrorY.force_clean() | | Valid keys: | | value [required=False] (value=number: x >= 0): | This is the single-sided span of error. This 'value', in | coordination with the 'type' specified, determines how long the | error bar is for ALL points. This is useful when 'type' is set to | something like 'percent'. To specify multiple error bar lengths, you | should use the 'array' key instead. | | array [required=False] (value=array-like of numbers, strings, datetimes | or number): | The array of error bar spans to be drawn. | | type [required=False] (value='data' | 'percent' | 'constant' | 'sqrt'): | Specify how the 'value' or 'array' key in this error bar will be | used to render the bars. Using 'data' will set error bar lengths to | the actual numbers specified in 'value' or 'array'. Using 'percent' | will set bar lengths to the percent of error associated with 'value' | or 'array'. Using 'constant' will set each error bar length to the | single value specified in 'array' or 'value'. Using 'sqrt' will set | each error bar length to the square root of the y data at each point | ('value' and 'array' do not apply). | | symmetric [required=False] (value=bool: True | False): | Toggle whether or not error bars are the same length in both | directions. | | valueminus [required=False] (value=number: x >= 0): | Only functional when 'symmetric' error bars are selected. Same as | 'value' but allows asymmetric error bars of specified length. | | arrayminus [required=False] (value=number: x >= 0): | Only functional when 'symmetric' error bars are selected. Same as | 'array' but allows asymmetric error bars of specified lengths. | | color [required=False] (value=str describing color): | str describing color | | Examples: | ["'green'", "'rgb(0, 255, 0)'", "'rgba(0, 255, 0, 0.3)'", | "'hsl(120,100%,50%)'", "'hsla(120,100%,50%,0.3)'"] | | opacity [required=False] (value=number: x in [0, 1]): | Sets the opacity, or transparency, of this object. Also known as the | alpha channel of colors, if the object's color is given in terms of | 'rgba', this does not need to be defined. | | thickness [required=False] (value=number: x >= 0): | Set the line thickness for the error bar. | | traceref [required=Aw, snap! Undocumented!] (value=Aw, snap! | Undocumented!): | Aw, snap! Undocumented! | | visible [required=False] (value=bool: True | False): | Toggles whether this will actually be visible in the rendered | figure. | | width [required=Aw, snap! Undocumented!] (value=number: x >= 0): | Set the width of the cross-bar at the end of the error bar. | | Method resolution order: | ErrorY | PlotlyDict | __builtin__.dict | __builtin__.object | | Methods inherited from PlotlyDict: | | __init__(self, *args, **kwargs) | | force_clean(self, caller=True) | Attempts to convert to graph_objs and call force_clean() on values. | | Calling force_clean() on a PlotlyDict will ensure that the object is | valid and may be sent to plotly. This process will also remove any | entries that end up with a length == 0. | | Careful! This will delete any invalid entries *silently*. | | get_data(self) | Returns the JSON for the plot with non-data elements stripped. | | strip_style(self) | Strip style from the current representation. | | All PlotlyDicts and PlotlyLists are guaranteed to survive the | stripping process, though they made be left empty. This is allowable. | | Keys that will be stripped in this process are tagged with | `'type': 'style'` in the INFO dictionary listed in graph_objs_meta.py. | | This process first attempts to convert nested collections from dicts | or lists to subclasses of PlotlyList/PlotlyDict. This process forces | a validation, which may throw exceptions. | | Then, each of these objects call `strip_style` on themselves and so | on, recursively until the entire structure has been validated and | stripped. | | to_graph_objs(self, caller=True) | Walk obj, convert dicts and lists to plotly graph objs. | | For each key in the object, if it corresponds to a special key that | should be associated with a graph object, the ordinary dict or list | will be reinitialized as a special PlotlyDict or PlotlyList of the | appropriate `kind`. | | to_string(self, level=0, indent=4, eol='\n', pretty=True, max_chars=80) | Returns a formatted string showing graph_obj constructors. | | Example: | | print obj.to_string() | | Keyword arguments: | level (default = 0) -- set number of indentations to start with | indent (default = 4) -- set indentation amount | eol (default = ' | ') -- set end of line character(s) | pretty (default = True) -- curtail long list output with a '...' | max_chars (default = 80) -- set max characters per line | | update(self, dict1=None, **dict2) | Update current dict with dict1 and then dict2. | | This recursively updates the structure of the original dictionary-like | object with the new entries in the second and third objects. This | allows users to update with large, nested structures. | | Note, because the dict2 packs up all the keyword arguments, you can | specify the changes as a list of keyword agruments. | | Examples: | # update with dict | obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1])) | update_dict = dict(title='new title', xaxis=dict(domain=[0,.8])) | obj.update(update_dict) | obj | {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}} | | # update with list of keyword arguments | obj = Layout(title='my title', xaxis=XAxis(range=[0,1], domain=[0,1])) | obj.update(title='new title', xaxis=dict(domain=[0,.8])) | obj | {'title': 'new title', 'xaxis': {'range': [0,1], 'domain': [0,.8]}} | | This 'fully' supports duck-typing in that the call signature is | identical, however this differs slightly from the normal update | method provided by Python's dictionaries. | | validate(self, caller=True) | Recursively check the validity of the keys in a PlotlyDict. | | The valid keys constitute the entries in each object | dictionary in INFO stored in graph_objs_meta.py. | | The validation process first requires that all nested collections be | converted to the appropriate subclass of PlotlyDict/PlotlyList. Then, | each of these objects call `validate` and so on, recursively, | until the entire object has been validated. | | ---------------------------------------------------------------------- | Data descriptors inherited from PlotlyDict: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes inherited from PlotlyDict: | | __metaclass__ = <class 'plotly.graph_objs.graph_objs.DictMeta'> | A meta class for PlotlyDict class creation. | | The sole purpose of this meta class is to properly create the __doc__ | attribute so that running help(Obj), where Obj is a subclass of PlotlyDict, | will return information about key-value pairs for that object. | | ---------------------------------------------------------------------- | Methods inherited from __builtin__.dict: | | __cmp__(...) | x.__cmp__(y) <==> cmp(x,y) | | __contains__(...) | D.__contains__(k) -> True if D has a key k, else False | | __delitem__(...) | x.__delitem__(y) <==> del x[y] | | __eq__(...) | x.__eq__(y) <==> x==y | | __ge__(...) | x.__ge__(y) <==> x>=y | | __getattribute__(...) | x.__getattribute__('name') <==> x.name | | __getitem__(...) | x.__getitem__(y) <==> x[y] | | __gt__(...) | x.__gt__(y) <==> x>y | | __iter__(...) | x.__iter__() <==> iter(x) | | __le__(...) | x.__le__(y) <==> x<=y | | __len__(...) | x.__len__() <==> len(x) | | __lt__(...) | x.__lt__(y) <==> x<y | | __ne__(...) | x.__ne__(y) <==> x!=y | | __repr__(...) | x.__repr__() <==> repr(x) | | __setitem__(...) | x.__setitem__(i, y) <==> x[i]=y | | __sizeof__(...) | D.__sizeof__() -> size of D in memory, in bytes | | clear(...) | D.clear() -> None. Remove all items from D. | | copy(...) | D.copy() -> a shallow copy of D | | fromkeys(...) | dict.fromkeys(S[,v]) -> New dict with keys from S and values equal to v. | v defaults to None. | | get(...) | D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None. | | has_key(...) | D.has_key(k) -> True if D has a key k, else False | | items(...) | D.items() -> list of D's (key, value) pairs, as 2-tuples | | iteritems(...) | D.iteritems() -> an iterator over the (key, value) items of D | | iterkeys(...) | D.iterkeys() -> an iterator over the keys of D | | itervalues(...) | D.itervalues() -> an iterator over the values of D | | keys(...) | D.keys() -> list of D's keys | | pop(...) | D.pop(k[,d]) -> v, remove specified key and return the corresponding value. | If key is not found, d is returned if given, otherwise KeyError is raised | | popitem(...) | D.popitem() -> (k, v), remove and return some (key, value) pair as a | 2-tuple; but raise KeyError if D is empty. | | setdefault(...) | D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D | | values(...) | D.values() -> list of D's values | | viewitems(...) | D.viewitems() -> a set-like object providing a view on D's items | | viewkeys(...) | D.viewkeys() -> a set-like object providing a view on D's keys | | viewvalues(...) | D.viewvalues() -> an object providing a view on D's values | | ---------------------------------------------------------------------- | Data and other attributes inherited from __builtin__.dict: | | __hash__ = None | | __new__ = <built-in method __new__ of type object> | T.__new__(S, ...) -> a new object with type S, a subtype of T
For both our x (time measurements) and y ($\text{CO}_2$) error bars we use data (i.e. not a constant percentage or value) to set their respective lengths. In our case, the 'symmetric'
key will be set to False
and both the 'array'
and 'arrayminus'
keys will be linked to arrays.
ErrorX
and ErrorY
will be ultimately linked to the 'error_x'
and 'error_y'
keys in Scatter
. So consider,
# Make instance of ErrorY object
my_error_y = ErrorY(type='data', # N.B. use data arrays!
symmetric=False, # N.B. asymmertic error bars!
array=err_co2_high, # above scatter points
arrayminus=err_co2_low, # below scatter points
color='black', # set color
thickness=1) # and line thickness
# Make instance of ErrorX object
my_error_x = ErrorX(type='data',
symmetric=False,
array=err_time_right, # right of scatter points
arrayminus=err_time_left, # left of scatter points
color='black',
thickness=1)
# Make instance of Scatter object
my_scatter = Scatter(mode='lines+markers', # line and marker pts along trace
x= time, # x-coords are in time
y= co2, # y-coords are in co2
error_y= my_error_y, # link y error bar object
error_x= my_error_x, # link x error bar object
marker= Marker(color='white', # white dots
size=10, # set size
line= Line(color='black', # bounded by black line
width=1.5)), # set width of line
line= Line(color='black', # black line in-between pts
width=2) # set width
)
# Link instance of Scatter to Data object
my_data = Data([my_scatter])
Next, add in a few style options to make our plot similar to the original:
# Dictionary of style options for both axes
my_axes_style = dict(showline=True, # show axis bounding line
mirror=True, # on both side
showgrid=False, # remove grid lines
zeroline=False, # remove thick zero line
autotick=False, # custom ticks
ticks='outside') # tick on outside of axis frame
# x-axis object
my_xaxis = XAxis(title='Age [Myr ago]',
range=[-0.9,25], # x-axis range
dtick=5) # distance between ticks
my_xaxis.update(my_axes_style)
my_yaxis = YAxis(title="Atmospheric CO2 [p.p.m]",
range=[0,500], # y-axis range
dtick=100) # distance between ticks
my_yaxis.update(my_axes_style)
my_layout = Layout(title='Record of atmospheric carbon dioxide for the past 25 Myr',
xaxis= my_xaxis,
yaxis= my_yaxis,
font= Font(family='Georgia, serif', # set global font family
color='black'), # and color
showlegend=False, # remove legend
autosize=False, # custom size
width=650, # in px
height=500 # in px
)
All we have left to do is to package the data and layout objects in a figure object and call iplot()
:
# Instance of figure object linked to data and layout objects
my_fig = Figure(data=my_data, layout=my_layout)
# Send figure object to Plotly with a filename
py.iplot(my_fig, filename='error_xy')
We just made a 14 year old plot come back to life
Now, let's improve it little bit by adding hover text and an annoation citing the data source.
# Make list of hover text by looping through x an y uncertainty arrays
my_text = ['<br>Uncertainty range in time: %s to %s Myr ago<br>\
Uncertainty range in CO2: %s to %s ppm'
% (low,high,left,right)
for low,high,left,right in zip(co2_low,co2_high,time_left,time_right)]
# Update the figure object
my_fig['data'].update(dict(text= my_text))
# Show info closest point on hover mode
my_fig['layout'].update(dict(hovermode='closest'))
Finally, add an annotation to the figure object:
from plotly.graph_objs import Annotation
# Make annotation object, include URL in text
url = "http://www.nature.com/nature/journal/v406/n6797/fig_tab/406695a0_F3.html"
my_anno = Annotation(text='Data Source: <a href="'+url+'" target="_blank">Nature 2000</a>',
xref='paper', # use paper coords
yref='paper',
x=0.01, # x position
y=0.03, # y positon
showarrow=False,
font= Font(size=14)
)
# Update figure object
my_fig['layout'].update(dict(annotations=[my_anno])) # N.B. accepts only lists
Great.
And now we are ready to replot our figure:
py.iplot(my_fig, filename='error_xy2')
About Plotly
Big thanks to
# CSS styling within IPython notebook
from IPython.core.display import HTML
def css_styling():
styles = open("../../python-user-guide/custom.css", "r").read()
return HTML(styles)
css_styling()