tornado chart

I never thought it would happen, but the
Matplotlib Gallery has for once failed me:
http://matplotlib.sourceforge.net/gallery.html

I was looking for an example of creating a nice
tornado chart:
http://code.enthought.com/projects/chaco/docs/html/user_manual/tutorial_1.html
http://www.tushar-mehta.com/excel/software/tornado/
http://www.juiceanalytics.com/writing/recreating-ny-times-cancer-graph/

A basic version will do, say along the lines of
the Chaco example.

Thanks for any leads,
Alan Isaac

Hi Alan,

Here’s an example based off the horizontal bar charts in the gallery. There may be a better way to align the y-tick labels (the example manually tweaks the x-offset), but I don’t know how to do it off the top of my head. Alternatively, you could put the ticks on the left and squish the space between subplots (using subplots_adjust(wspace=0) but then you run into the issue of overlapping x-tick labels.

Hope that helps,

-Tony

tornado chart example

import numpy as np

import matplotlib.pyplot as plt

people = (‘Tom’, ‘Dick’, ‘Harry’, ‘Slim’, ‘Jim’)

num_people = len(people)

time_spent = np.random.uniform(low=5, high=100, size=num_people)

proficiency = np.abs(time_spent / 12. + np.random.normal(size=num_people))

pos = np.arange(num_people) + .5 # bars centered on the y axis

fig, (ax_left, ax_right) = plt.subplots(ncols=2)

ax_left.barh(pos, time_spent, align=‘center’, facecolor=‘cornflowerblue’)

ax_left.set_yticks()

ax_left.set_xlabel(‘Hours spent’)

ax_left.invert_xaxis()

ax_right.barh(pos, proficiency, align=‘center’, facecolor=‘lemonchiffon’)

ax_right.set_yticks(pos)

x moves tick labels relative to left edge of axes in axes units

ax_right.set_yticklabels(people, ha=‘center’, x=-0.08)

ax_right.set_xlabel(‘Proficiency’)

plt.suptitle(‘Learning Python’)

plt.show()

···

On Thu, Jun 21, 2012 at 6:42 PM, Alan G Isaac <alan.isaac@…287…> wrote:

I never thought it would happen, but the

Matplotlib Gallery has for once failed me:

http://matplotlib.sourceforge.net/gallery.html

I was looking for an example of creating a nice

tornado chart:

http://code.enthought.com/projects/chaco/docs/html/user_manual/tutorial_1.html

http://www.tushar-mehta.com/excel/software/tornado/

http://www.juiceanalytics.com/writing/recreating-ny-times-cancer-graph/

A basic version will do, say along the lines of

the Chaco example.

Thanks for any leads,

Alan Isaac

Pretty good, really!
More than just a starting point.

Thanks,
Alan

···

On 6/21/2012 10:24 PM, Tony Yu wrote:

Here's an example based off the horizontal bar charts in the gallery.

And here is a modified example a little closer visually

# tornado chart example; inspired by
# http://www.nytimes.com/imagepages/2007/07/29/health/29cancer.graph.web.html
# and sample code from Tony Yu
import numpy as np
import matplotlib.pyplot as plt

cancers = [
    'Kidney cancer',
    'Bladder cancer',
    'Esophageal cancer',
    'Ovarian cancer',
    'Liver cancer',
    "Non-Hodgkin's\nlymphoma",
    'Leukemia',
    'Prostate cancer',
    'Pancreatic cancer',
    'Breast cancer',
    'Colorectal cancer',
    'Lung cancer',
    ]

num_cancers = len(cancers)

# generate some random data for the graphs (TODO; put real data here)
new_cases_men = np.random.uniform(low=20e3, high=200e3, size=num_cancers)
new_cases_women = np.random.uniform(low=20e3, high=200e3, size=num_cancers)
deaths_women = np.random.rand(num_cancers)*new_cases_women
deaths_men = np.random.rand(num_cancers)*new_cases_men

# force these values where the labels happen to make sure they are
# positioned nicely
new_cases_men[-1] = 120e3
new_cases_men[-2] = 55e3
deaths_men[-1] = 80e3

# bars centered on the y axis
pos = np.arange(num_cancers) + .5

# make the left and right axes for women and men
fig = plt.figure(facecolor='white', edgecolor='none')
ax_women = fig.add_axes([0.05, 0.1, 0.35, 0.8])
ax_men = fig.add_axes([0.6, 0.1, 0.35, 0.8])

ax_men.set_xticks(np.arange(50e3, 201e3, 50e3))
ax_women.set_xticks(np.arange(50e3, 201e3, 50e3))

# turn off the axes spines except on the inside y-axis
for loc, spine in ax_women.spines.iteritems():
    if loc!='right':
        spine.set_color('none') # don't draw spine

for loc, spine in ax_men.spines.iteritems():
    if loc!='left':
        spine.set_color('none') # don't draw spine

# just tick on the top
ax_women.xaxis.set_ticks_position('top')
ax_men.xaxis.set_ticks_position('top')

# make the women's graphs
ax_women.barh(pos, new_cases_women, align='center',
facecolor='#DBE3C2', edgecolor='None')
ax_women.barh(pos, deaths_women, align='center', facecolor='#7E895F',
height=0.5, edgecolor='None')
ax_women.set_yticks()
ax_women.invert_xaxis()

# make the men's graphs
ax_men.barh(pos, new_cases_men, align='center', facecolor='#D8E2E1',
edgecolor='None')
ax_men.barh(pos, deaths_men, align='center', facecolor='#6D7D72',
height=0.5, edgecolor='None')
ax_men.set_yticks()

# we want the cancer labels to be centered in the fig coord system and
# centered w/ respect to the bars so we use a custom transform
import matplotlib.transforms as transforms
transform = transforms.blended_transform_factory(
    fig.transFigure, ax_men.transData)
for i, label in enumerate(cancers):
    ax_men.text(0.5, i+0.5, label, ha='center', va='center',
transform=transform)

# the axes titles are in axes coords, so x=0, y=1.025 is on the left
# side of the axes, just above, x=1.0, y=1.025 is the right side of the
# axes, just above
ax_men.set_title('MEN', x=0.0, y=1.025, fontsize=12)
ax_women.set_title('WOMEN', x=1.0, y=1.025, fontsize=12)

# the fig suptile is in fig coords, so 0.98 is the far right; we right
align the text
fig.suptitle('July 29, 2007', x=0.98, ha='right')

# now add the annotations
ax_men.annotate('New Cases', xy=(0.95*new_cases_men[-1], num_cancers-0.5),
                xycoords='data',
                xytext=(20, 0), textcoords='offset points',
                size=12,
                va='center',
                arrowprops=dict(arrowstyle="->"),
                )

# a curved arrow for the deaths annotation
ax_men.annotate('Deaths', xy=(0.95*deaths_men[-1], num_cancers-0.5),
                xycoords='data',
                xytext=(40, -20), textcoords='offset points',
                size=12,
                va='center',
                arrowprops=dict(arrowstyle="->",

connectionstyle="angle,angleA=0,angleB=90,rad=3"),

                )

plt.show()

···

On Fri, Jun 22, 2012 at 2:48 PM, Alan G Isaac <alan.isaac@...287...> wrote:

On 6/21/2012 10:24 PM, Tony Yu wrote:

Here's an example based off the horizontal bar charts in the gallery.

Pretty good, really!
More than just a starting point.

1 Like

With some real data and a bit of polish to the source, I would love to add this to the gallery. Real data is a must, of course-- men with ovarian cancer just look silly.

Ben Root

···

On Friday, June 22, 2012, John Hunter wrote:

On Fri, Jun 22, 2012 at 2:48 PM, Alan G Isaac <alan.isaac@…287…> wrote:

On 6/21/2012 10:24 PM, Tony Yu wrote:

Here’s an example based off the horizontal bar charts in the gallery.

Pretty good, really!

More than just a starting point.

And here is a modified example a little closer visually

tornado chart example; inspired by

http://www.nytimes.com/imagepages/2007/07/29/health/29cancer.graph.web.html

and sample code from Tony Yu

import numpy as np

import matplotlib.pyplot as plt

cancers = [

'Kidney cancer',

'Bladder cancer',

'Esophageal cancer',

'Ovarian cancer',

'Liver cancer',

"Non-Hodgkin's\nlymphoma",

'Leukemia',

'Prostate cancer',

'Pancreatic cancer',

'Breast cancer',

'Colorectal cancer',

'Lung cancer',

]

num_cancers = len(cancers)

generate some random data for the graphs (TODO; put real data here)

new_cases_men = np.random.uniform(low=20e3, high=200e3, size=num_cancers)

new_cases_women = np.random.uniform(low=20e3, high=200e3, size=num_cancers)

deaths_women = np.random.rand(num_cancers)*new_cases_women

deaths_men = np.random.rand(num_cancers)*new_cases_men

force these values where the labels happen to make sure they are

positioned nicely

new_cases_men[-1] = 120e3

new_cases_men[-2] = 55e3

deaths_men[-1] = 80e3

bars centered on the y axis

pos = np.arange(num_cancers) + .5

make the left and right axes for women and men

fig = plt.figure(facecolor=‘white’, edgecolor=‘none’)

ax_women = fig.add_axes([0.05, 0.1, 0.35, 0.8])

ax_men = fig.add_axes([0.6, 0.1, 0.35, 0.8])

ax_men.set_xticks(np.arange(50e3, 201e3, 50e3))

ax_women.set_xticks(np.arange(50e3, 201e3, 50e3))

turn off the axes spines except on the inside y-axis

for loc, spine in ax_women.spines.iteritems():

if loc!='right':

    spine.set_color('none') # don't draw spine

for loc, spine in ax_men.spines.iteritems():

if loc!='left':

    spine.set_color('none') # don't draw spine

just tick on the top

ax_women.xaxis.set_ticks_position(‘top’)

ax_men.xaxis.set_ticks_position(‘top’)

make the women’s graphs

ax_women.barh(pos, new_cases_women, align=‘center’,

facecolor=‘#DBE3C2’, edgecolor=‘None’)

ax_women.barh(pos, deaths_women, align=‘center’, facecolor=‘#7E895F’,

height=0.5, edgecolor=‘None’)

ax_women.set_yticks()

ax_women.invert_xaxis()

make the men’s graphs

ax_men.barh(pos, new_cases_men, align=‘center’, facecolor=‘#D8E2E1’,

edgecolor=‘None’)

ax_men.barh(pos, deaths_men, align=‘center’, facecolor=‘#6D7D72’,

height=0.5, edgecolor=‘None’)

ax_men.set_yticks()

we want the cancer labels to be centered in the fig coord system and

centered w/ respect to the bars so we use a custom transform

import matplotlib.transforms as transforms

transform = transforms.blended_transform_factory(

fig.transFigure, ax_men.transData)

for i, label in enumerate(cancers):

ax_men.text(0.5, i+0.5, label, ha='center', va='center',

transform=transform)

the axes titles are in axes coords, so x=0, y=1.025 is on the left

side of the axes, just above, x=1.0, y=1.025 is the right side of the

axes, just above

ax_men.set_title(‘MEN’, x=0.0, y=1.025, fontsize=12)

ax_women.set_title(‘WOMEN’, x=1.0, y=1.025, fontsize=12)

the fig suptile is in fig coords, so 0.98 is the far right; we right

align the text

fig.suptitle(‘July 29, 2007’, x=0.98, ha=‘right’)

now add the annotations

ax_men.annotate(‘New Cases’, xy=(0.95*new_cases_men[-1], num_cancers-0.5),

            xycoords='data',

            xytext=(20, 0), textcoords='offset points',

            size=12,

            va='center',

            arrowprops=dict(arrowstyle="->"),

            )

a curved arrow for the deaths annotation

ax_men.annotate(‘Deaths’, xy=(0.95*deaths_men[-1], num_cancers-0.5),

            xycoords='data',

            xytext=(40, -20), textcoords='offset points',

            size=12,

            va='center',

            arrowprops=dict(arrowstyle="->",

connectionstyle=“angle,angleA=0,angleB=90,rad=3”),

            )

plt.show()

I did it once and posted it to the list but never found the time to add it to the official gallery (my bad):

http://www.loria.fr/~rougier/coding/gallery/showcase/showcase-3-large.png
http://www.loria.fr/~rougier/coding/gallery/

Nicolas

···

On Jun 23, 2012, at 5:36 , Benjamin Root wrote:

On Friday, June 22, 2012, John Hunter wrote:
On Fri, Jun 22, 2012 at 2:48 PM, Alan G Isaac <alan.isaac@...287...> wrote:
> On 6/21/2012 10:24 PM, Tony Yu wrote:
>> Here's an example based off the horizontal bar charts in the gallery.
>
> Pretty good, really!
> More than just a starting point.

And here is a modified example a little closer visually

# tornado chart example; inspired by
# http://www.nytimes.com/imagepages/2007/07/29/health/29cancer.graph.web.html
# and sample code from Tony Yu
import numpy as np
import matplotlib.pyplot as plt

cancers = [
   'Kidney cancer',
   'Bladder cancer',
   'Esophageal cancer',
   'Ovarian cancer',
   'Liver cancer',
   "Non-Hodgkin's\nlymphoma",
   'Leukemia',
   'Prostate cancer',
   'Pancreatic cancer',
   'Breast cancer',
   'Colorectal cancer',
   'Lung cancer',
   ]

num_cancers = len(cancers)

# generate some random data for the graphs (TODO; put real data here)
new_cases_men = np.random.uniform(low=20e3, high=200e3, size=num_cancers)
new_cases_women = np.random.uniform(low=20e3, high=200e3, size=num_cancers)
deaths_women = np.random.rand(num_cancers)*new_cases_women
deaths_men = np.random.rand(num_cancers)*new_cases_men

# force these values where the labels happen to make sure they are
# positioned nicely
new_cases_men[-1] = 120e3
new_cases_men[-2] = 55e3
deaths_men[-1] = 80e3

# bars centered on the y axis
pos = np.arange(num_cancers) + .5

# make the left and right axes for women and men
fig = plt.figure(facecolor='white', edgecolor='none')
ax_women = fig.add_axes([0.05, 0.1, 0.35, 0.8])
ax_men = fig.add_axes([0.6, 0.1, 0.35, 0.8])

ax_men.set_xticks(np.arange(50e3, 201e3, 50e3))
ax_women.set_xticks(np.arange(50e3, 201e3, 50e3))

# turn off the axes spines except on the inside y-axis
for loc, spine in ax_women.spines.iteritems():
   if loc!='right':
       spine.set_color('none') # don't draw spine

for loc, spine in ax_men.spines.iteritems():
   if loc!='left':
       spine.set_color('none') # don't draw spine

# just tick on the top
ax_women.xaxis.set_ticks_position('top')
ax_men.xaxis.set_ticks_position('top')

# make the women's graphs
ax_women.barh(pos, new_cases_women, align='center',
facecolor='#DBE3C2', edgecolor='None')
ax_women.barh(pos, deaths_women, align='center', facecolor='#7E895F',
height=0.5, edgecolor='None')
ax_women.set_yticks()
ax_women.invert_xaxis()

# make the men's graphs
ax_men.barh(pos, new_cases_men, align='center', facecolor='#D8E2E1',
edgecolor='None')
ax_men.barh(pos, deaths_men, align='center', facecolor='#6D7D72',
height=0.5, edgecolor='None')
ax_men.set_yticks()

# we want the cancer labels to be centered in the fig coord system and
# centered w/ respect to the bars so we use a custom transform
import matplotlib.transforms as transforms
transform = transforms.blended_transform_factory(
   fig.transFigure, ax_men.transData)
for i, label in enumerate(cancers):
   ax_men.text(0.5, i+0.5, label, ha='center', va='center',
transform=transform)

# the axes titles are in axes coords, so x=0, y=1.025 is on the left
# side of the axes, just above, x=1.0, y=1.025 is the right side of the
# axes, just above
ax_men.set_title('MEN', x=0.0, y=1.025, fontsize=12)
ax_women.set_title('WOMEN', x=1.0, y=1.025, fontsize=12)

# the fig suptile is in fig coords, so 0.98 is the far right; we right
align the text
fig.suptitle('July 29, 2007', x=0.98, ha='right')

# now add the annotations
ax_men.annotate('New Cases', xy=(0.95*new_cases_men[-1], num_cancers-0.5),
               xycoords='data',
               xytext=(20, 0), textcoords='offset points',
               size=12,
               va='center',
               arrowprops=dict(arrowstyle="->"),
               )

# a curved arrow for the deaths annotation
ax_men.annotate('Deaths', xy=(0.95*deaths_men[-1], num_cancers-0.5),
               xycoords='data',
               xytext=(40, -20), textcoords='offset points',
               size=12,
               va='center',
               arrowprops=dict(arrowstyle="->",

connectionstyle="angle,angleA=0,angleB=90,rad=3"),

               )

plt.show()

With some real data and a bit of polish to the source, I would love to add this to the gallery. Real data is a must, of course-- men with ovarian cancer just look silly.

Ben Root
------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and
threat landscape has changed and how IT managers can respond. Discussions
will include endpoint security, mobile security and the latest in malware
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options