├── .github ├── FUNDING.yml └── workflows │ └── python-package.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── bar_chart_race ├── __init__.py ├── _bar_chart_race.py ├── _bar_chart_race_plotly.py ├── _codes │ ├── code_value.csv │ ├── country.csv │ └── nfl.csv ├── _colormaps.py ├── _common_chart.py ├── _func_animation.py ├── _line_chart_race.py ├── _pandas_accessor.py └── _utils.py ├── data ├── baseball.csv ├── covid19.csv ├── covid19_tutorial.csv └── urban_pop.csv ├── docs ├── css │ └── style.css ├── data_preparation.md ├── images │ ├── bcr_notebook.png │ ├── covid19_horiz.gif │ ├── prepare_long.png │ └── wide_data.png ├── index.md ├── installation.md ├── tutorial.md ├── upcoming.md └── whats_new.md ├── mkdocs.yml ├── setup.py └── tests ├── __init__.py ├── data └── lcr_data.csv ├── test_bar_charts.py ├── test_line_chart.py ├── test_plotly.py ├── test_prepare.py └── videos └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.dunderdata.com'] 2 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | python-version: [3.6, 3.7, 3.8] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Download ffmpeg linux 28 | if: ${{ matrix.os == 'ubuntu-latest' }} 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install ffmpeg 32 | sudo apt install ffmpeg 33 | - name: Download ffmpeg mac 34 | if: ${{ matrix.os == 'macos-latest' }} 35 | run: brew install ffmpeg 36 | - name: Download ffmpeg windows 37 | if: ${{ matrix.os == 'windows-latest' }} 38 | run: choco install ffmpeg --no-progress 39 | - name: Install dependencies 40 | run: | 41 | python -m pip install --upgrade pip 42 | pip install pytest matplotlib==3.2 pandas plotly 43 | - name: Test with pytest 44 | run: | 45 | pytest 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | notebooks/ 3 | .DS_Store 4 | *egg-info/ 5 | build/ 6 | dist/ 7 | site/ 8 | *.mp4 9 | docs/ 10 | data/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 dexplo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE bar_chart_race/_codes/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bar Chart Race 2 | 3 | [](https://pypi.org/project/bar_chart_race) 4 | [](LICENSE) 5 | 6 | Make animated bar and line chart races in Python with matplotlib or plotly. 7 | 8 |  9 | 10 | ## Official Documentation 11 | 12 | Visit the [bar_chart_race official documentation](https://www.dexplo.org/bar_chart_race) for detailed usage instructions. 13 | 14 | ## Installation 15 | 16 | Install with either: 17 | 18 | * `pip install bar_chart_race` 19 | * `conda install -c conda-forge bar_chart_race` 20 | 21 | ## Quickstart 22 | 23 | Must begin with a pandas DataFrame containing 'wide' data where: 24 | 25 | * Every row represents a single period of time 26 | * Each column holds the value for a particular category 27 | * The index contains the time component (optional) 28 | 29 | The data below is an example of properly formatted data. It shows total deaths from COVID-19 for several countries by date. 30 | 31 |  32 | 33 | ### Create bar and line chart races 34 | 35 | There are three core functions available to construct the animations. 36 | 37 | * `bar_chart_race` 38 | * `bar_chart_race_plotly` 39 | * `line_chart_race` 40 | 41 | The above animation was created with the help of matplotlib using the following call to `bar_chart_race`. 42 | 43 | ```python 44 | import bar_chart_race as bcr 45 | df = bcr.load_dataset('covid19_tutorial') 46 | bcr.bar_chart_race( 47 | df=df, 48 | filename='../docs/images/covid19_horiz.gif', 49 | orientation='h', 50 | sort='desc', 51 | n_bars=8, 52 | fixed_order=False, 53 | fixed_max=True, 54 | steps_per_period=20, 55 | period_length=500, 56 | end_period_pause=0, 57 | interpolate_period=False, 58 | period_label={'x': .98, 'y': .3, 'ha': 'right', 'va': 'center'}, 59 | period_template='%B %d, %Y', 60 | period_summary_func=lambda v, r: {'x': .98, 'y': .2, 61 | 's': f'Total deaths: {v.sum():,.0f}', 62 | 'ha': 'right', 'size': 11}, 63 | perpendicular_bar_func='median', 64 | colors='dark12', 65 | title='COVID-19 Deaths by Country', 66 | bar_size=.95, 67 | bar_textposition='inside', 68 | bar_texttemplate='{x:,.0f}', 69 | bar_label_font=7, 70 | tick_label_font=7, 71 | tick_template='{x:,.0f}', 72 | shared_fontdict=None, 73 | scale='linear', 74 | fig=None, 75 | writer=None, 76 | bar_kwargs={'alpha': .7}, 77 | fig_kwargs={'figsize': (6, 3.5), 'dpi': 144}, 78 | filter_column_colors=False) 79 | ``` 80 | 81 | ### Save animation to disk or embed into a Jupyter Notebook 82 | 83 | If you are working within a Jupyter Notebook, leave the `filename` as `None` and it will be automatically embedded into a Jupyter Notebook. 84 | 85 | ```python 86 | bcr.bar_chart_race(df=df, filename=None) 87 | ``` 88 | 89 |  90 | 91 | ### Customization 92 | 93 | There are many options to customize the bar chart race to get the animation you desire. Below, we have an animation where the maximum x-value and order of the bars are set for the entire duration. A custom summary label and perpendicular bar of the median is also added. 94 | 95 | ```python 96 | def period_summary(values, ranks): 97 | top2 = values.nlargest(2) 98 | leader = top2.index[0] 99 | lead = top2.iloc[0] - top2.iloc[1] 100 | s = f'{leader} by {lead:.0f}' 101 | return {'s': s, 'x': .99, 'y': .03, 'ha': 'right', 'size': 8} 102 | 103 | df_baseball = bcr.load_dataset('baseball').pivot(index='year', 104 | columns='name', 105 | values='hr') 106 | df_baseball.bcr.bar_chart_race( 107 | period_length=1000, 108 | fixed_max=True, 109 | fixed_order=True, 110 | n_bars=10, 111 | period_summary_func=period_summary, 112 | period_label={'x': .99, 'y': .1}, 113 | period_template='Season {x:,.0f}', 114 | title='Top 10 Home Run Hitters by Season Played') 115 | ``` 116 | 117 |  118 | -------------------------------------------------------------------------------- /bar_chart_race/__init__.py: -------------------------------------------------------------------------------- 1 | from ._bar_chart_race import bar_chart_race 2 | import importlib 3 | if importlib.util.find_spec('plotly'): 4 | from ._bar_chart_race_plotly import bar_chart_race_plotly 5 | from ._line_chart_race import line_chart_race 6 | from ._utils import load_dataset, prepare_wide_data, prepare_long_data 7 | from . import _pandas_accessor 8 | 9 | __version__ = '0.2.0' 10 | __all__ = [ 11 | 'bar_chart_race', 12 | 'bar_chart_race_plotly', 13 | 'load_dataset', 14 | 'prepare_wide_data', 15 | 'prepare_long_data', 16 | 'line_chart_race'] 17 | -------------------------------------------------------------------------------- /bar_chart_race/_bar_chart_race_plotly.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import plotly.graph_objects as go 6 | import plotly 7 | 8 | from ._utils import prepare_wide_data 9 | 10 | 11 | class _BarChartRace: 12 | 13 | def __init__(self, df, filename, orientation, sort, n_bars, fixed_order, fixed_max, 14 | steps_per_period, period_length, end_period_pause, interpolate_period, 15 | period_label, period_template, period_summary_func, perpendicular_bar_func, 16 | colors, title, bar_size, bar_textposition, bar_texttemplate, bar_label_font, 17 | tick_label_font, hovertemplate, slider, scale, bar_kwargs, layout_kwargs, 18 | write_html_kwargs, filter_column_colors): 19 | self.filename = filename 20 | self.extension = self.get_extension() 21 | self.orientation = orientation 22 | self.sort = sort 23 | self.n_bars = n_bars or df.shape[1] 24 | self.fixed_order = fixed_order 25 | self.fixed_max = fixed_max 26 | self.steps_per_period = steps_per_period 27 | self.period_length = period_length 28 | self.end_period_pause = end_period_pause 29 | self.interpolate_period = interpolate_period 30 | self.period_label = self.get_period_label(period_label) 31 | self.period_template = period_template 32 | self.period_summary_func = period_summary_func 33 | self.perpendicular_bar_func = perpendicular_bar_func 34 | self.title = self.get_title(title) 35 | self.bar_size = bar_size 36 | self.bar_textposition = bar_textposition 37 | self.bar_texttemplate = self.get_bar_texttemplate(bar_texttemplate) 38 | self.bar_label_font = self.get_font(bar_label_font) 39 | self.tick_label_font = self.get_font(tick_label_font) 40 | self.hovertemplate = self.get_hovertemplate(hovertemplate) 41 | self.slider = slider 42 | self.scale = scale 43 | self.duration = self.period_length / steps_per_period 44 | self.write_html_kwargs = write_html_kwargs or {} 45 | self.filter_column_colors = filter_column_colors 46 | 47 | self.validate_params() 48 | self.bar_kwargs = self.get_bar_kwargs(bar_kwargs) 49 | self.layout_kwargs = self.get_layout_kwargs(layout_kwargs) 50 | self.df_values, self.df_ranks = self.prepare_data(df) 51 | self.col_filt = self.get_col_filt() 52 | self.bar_colors = self.get_bar_colors(colors) 53 | self.set_fixed_max_limits() 54 | self.str_index = self.df_values.index.astype('str') 55 | 56 | def get_extension(self): 57 | if self.filename: 58 | return self.filename.split('.')[-1] 59 | 60 | def get_bar_texttemplate(self, bar_texttemplate): 61 | if bar_texttemplate is None: 62 | bar_texttemplate = '%{x:,.0f}' if self.orientation == 'h' else '%{y:,.0f}' 63 | return bar_texttemplate 64 | 65 | def validate_params(self): 66 | if isinstance(self.filename, str): 67 | if '.' not in self.filename: 68 | raise ValueError('`filename` must have an extension') 69 | elif self.filename is not None: 70 | raise TypeError('`filename` must be None or a string') 71 | 72 | if self.sort not in ('asc', 'desc'): 73 | raise ValueError('`sort` must be "asc" or "desc"') 74 | 75 | if self.orientation not in ('h', 'v'): 76 | raise ValueError('`orientation` must be "h" or "v"') 77 | 78 | def get_bar_kwargs(self, bar_kwargs): 79 | if bar_kwargs is None: 80 | return {'opacity': .8} 81 | elif isinstance(bar_kwargs, dict): 82 | if 'opacity' not in bar_kwargs: 83 | bar_kwargs['opacity'] = .8 84 | return bar_kwargs 85 | raise TypeError('`bar_kwargs` must be None or a dictionary mapping `go.Bar` parameters ' 86 | 'to values.') 87 | 88 | def get_layout_kwargs(self, layout_kwargs): 89 | if layout_kwargs is None: 90 | return {'showlegend': False} 91 | elif isinstance(layout_kwargs, dict): 92 | if {'xaxis', 'yaxis', 'annotations'} & layout_kwargs.keys(): 93 | raise ValueError('`layout_kwargs` cannot contain "xaxis", "yaxis", or ' 94 | ' "annotations".') 95 | if 'showlegend' not in layout_kwargs: 96 | layout_kwargs['showlegend'] = False 97 | return layout_kwargs 98 | elif isinstance(layout_kwargs, plotly.graph_objs._layout.Layout): 99 | return self.get_layout_kwargs(layout_kwargs.to_plotly_json()) 100 | raise TypeError('`layout_kwargs` must be None, a dictionary mapping ' 101 | '`go.Layout` parameters to values or an instance of `go.Layout`.') 102 | 103 | def get_period_label(self, period_label): 104 | if period_label is False: 105 | return False 106 | 107 | default_period_label = {'xref': 'paper', 'yref': 'paper', 'font': {'size': 20}, 108 | 'xanchor': 'right', 'showarrow': False} 109 | if self.orientation == 'h': 110 | default_period_label['x'] = .95 111 | default_period_label['y'] = .15 if self.sort == 'desc' else .85 112 | else: 113 | default_period_label['x'] = .95 if self.sort == 'desc' else .05 114 | default_period_label['y'] = .85 115 | default_period_label['xanchor'] = 'left' if self.sort == 'asc' else 'right' 116 | 117 | if period_label is True: 118 | return default_period_label 119 | elif isinstance(period_label, dict): 120 | period_label = {**default_period_label, **period_label} 121 | else: 122 | raise TypeError('`period_label` must be a boolean or dictionary') 123 | 124 | return period_label 125 | 126 | def get_title(self, title): 127 | if title is None: 128 | return 129 | if isinstance(title, str): 130 | return {'text': title, 'y': 1, 'x': .5, 'xref': 'paper', 'yref': 'paper', 131 | 'pad': {'b': 10}, 132 | 'xanchor': 'center', 'yanchor': 'bottom'} 133 | elif isinstance(title, (dict, plotly.graph_objects.layout.Title)): 134 | return title 135 | raise TypeError('`title` must be a string, dictionary, or ' 136 | '`plotly.graph_objects.layout.Title` instance') 137 | 138 | def get_font(self, font): 139 | if font is None: 140 | font = {'size': 12} 141 | elif isinstance(font, (int, float)): 142 | font = {'size': font} 143 | elif not isinstance(font, dict): 144 | raise TypeError('`font` must be a number or dictionary of font properties') 145 | return font 146 | 147 | def get_hovertemplate(self, hovertemplate): 148 | if hovertemplate is None: 149 | if self.orientation == 'h': 150 | return '%{y} - %{x:,.0f}' 151 | return '%{x} - %{y:,.0f}' 152 | return hovertemplate 153 | 154 | def prepare_data(self, df): 155 | if self.fixed_order is True: 156 | last_values = df.iloc[-1].sort_values(ascending=False) 157 | cols = last_values.iloc[:self.n_bars].index 158 | df = df[cols] 159 | elif isinstance(self.fixed_order, list): 160 | cols = self.fixed_order 161 | df = df[cols] 162 | self.n_bars = min(len(cols), self.n_bars) 163 | 164 | compute_ranks = self.fixed_order is False 165 | dfs = prepare_wide_data(df, orientation=self.orientation, sort=self.sort, 166 | n_bars=self.n_bars, interpolate_period=self.interpolate_period, 167 | steps_per_period=self.steps_per_period, compute_ranks=compute_ranks) 168 | if isinstance(dfs, tuple): 169 | df_values, df_ranks = dfs 170 | else: 171 | df_values = dfs 172 | 173 | if self.fixed_order: 174 | n = df_values.shape[1] + 1 175 | m = df_values.shape[0] 176 | rank_row = np.arange(1, n) 177 | if (self.sort == 'desc' and self.orientation == 'h') or \ 178 | (self.sort == 'asc' and self.orientation == 'v'): 179 | rank_row = rank_row[::-1] 180 | 181 | ranks_arr = np.repeat(rank_row.reshape(1, -1), m, axis=0) 182 | df_ranks = pd.DataFrame(data=ranks_arr, columns=cols) 183 | 184 | return df_values, df_ranks 185 | 186 | def get_col_filt(self): 187 | col_filt = pd.Series([True] * self.df_values.shape[1]) 188 | if self.n_bars < self.df_ranks.shape[1]: 189 | orient_sort = self.orientation, self.sort 190 | if orient_sort in [('h', 'asc'), ('v', 'desc')]: 191 | # 1 is high 192 | col_filt = (self.df_ranks < self.n_bars + .99).any() 193 | else: 194 | # 1 is low 195 | col_filt = (self.df_ranks > 0).any() 196 | 197 | if self.filter_column_colors and not col_filt.all(): 198 | self.df_values = self.df_values.loc[:, col_filt] 199 | self.df_ranks = self.df_ranks.loc[:, col_filt] 200 | return col_filt 201 | 202 | def get_bar_colors(self, colors): 203 | if colors is None: 204 | colors = 'dark12' 205 | if self.df_values.shape[1] > 10: 206 | colors = 'dark24' 207 | 208 | if isinstance(colors, str): 209 | from ._colormaps import colormaps 210 | try: 211 | bar_colors = colormaps[colors.lower()] 212 | except KeyError: 213 | raise KeyError(f'Colormap {colors} does not exist. Here are the ' 214 | f'possible colormaps: {colormaps.keys()}') 215 | elif isinstance(colors, list): 216 | bar_colors = colors 217 | elif isinstance(colors, tuple): 218 | bar_colors = list(colors) 219 | elif hasattr(colors, 'tolist'): 220 | bar_colors = colors.tolist() 221 | else: 222 | raise TypeError('`colors` must be a string name of a colormap or ' 223 | 'sequence of colors.') 224 | 225 | # bar_colors is now a list 226 | n = len(bar_colors) 227 | orig_bar_colors = bar_colors 228 | if self.df_values.shape[1] > n: 229 | bar_colors = bar_colors * (self.df_values.shape[1] // n + 1) 230 | bar_colors = np.array(bar_colors[:self.df_values.shape[1]]) 231 | 232 | # plotly uses 0, 255 rgb colors, matplotlib is 0 to 1 233 | if bar_colors.dtype.kind == 'f' and bar_colors.shape[1] == 3 and (bar_colors <= 1).all(): 234 | bar_colors = pd.DataFrame(bar_colors).astype('str') 235 | bar_colors = bar_colors.apply(lambda x: ','.join(x), axis = 1) 236 | bar_colors = ('rgb(' + bar_colors + ')').values 237 | 238 | if not self.filter_column_colors: 239 | if not self.col_filt.all(): 240 | col_idx = np.where(self.col_filt)[0] % n 241 | col_idx_ct = np.bincount(col_idx, minlength=n) 242 | num_cols = max(self.col_filt.sum(), n) 243 | exp_ct = np.bincount(np.arange(num_cols) % n, minlength=n) 244 | if (col_idx_ct > exp_ct).any(): 245 | warnings.warn("Some of your columns never make an appearance in the animation. " 246 | "To reduce color repetition, set `filter_column_colors` to `True`") 247 | return bar_colors 248 | 249 | def set_fixed_max_limits(self): 250 | label_limit = (.2, self.n_bars + .8) 251 | value_limit = None 252 | min_val = 1 if self.scale == 'log' else 0 253 | if self.fixed_max: 254 | value_limit = [min_val, self.df_values.max().max() * 1.1] 255 | 256 | if self.orientation == 'h': 257 | self.xlimit = value_limit 258 | self.ylimit = label_limit 259 | else: 260 | self.xlimit = label_limit 261 | self.ylimit = value_limit 262 | 263 | def set_value_limit(self, bar_vals): 264 | min_val = 1 if self.scale == 'log' else 0 265 | if not self.fixed_max: 266 | value_limit = [min_val, bar_vals.max() * 1.1] 267 | 268 | if self.orientation == 'h': 269 | self.xlimit = value_limit 270 | else: 271 | self.ylimit = value_limit 272 | 273 | def get_frames(self): 274 | frames = [] 275 | slider_steps = [] 276 | for i in range(len(self.df_values)): 277 | bar_locs = self.df_ranks.iloc[i].values 278 | top_filt = (bar_locs >= 0) & (bar_locs < self.n_bars + 1) 279 | bar_vals = self.df_values.iloc[i].values 280 | bar_vals[bar_locs == 0] = 0 281 | bar_vals[bar_locs == self.n_bars + 1] = 0 282 | # self.set_value_limit(bar_vals) # plotly bug? not updating range 283 | 284 | cols = self.df_values.columns.values.copy() 285 | cols[bar_locs == 0] = ' ' 286 | colors = self.bar_colors 287 | bar_locs = bar_locs + np.random.rand(len(bar_locs)) / 10_000 # done to prevent stacking of bars 288 | x, y = (bar_vals, bar_locs) if self.orientation == 'h' else (bar_locs, bar_vals) 289 | 290 | label_axis = dict(tickmode='array', tickvals=bar_locs, ticktext=cols, 291 | tickfont=self.tick_label_font) 292 | 293 | label_axis['range'] = self.ylimit if self.orientation == 'h' else self.xlimit 294 | if self.orientation == 'v': 295 | label_axis['tickangle'] = -90 296 | 297 | value_axis = dict(showgrid=True, type=self.scale)#, tickformat=',.0f') 298 | value_axis['range'] = self.xlimit if self.orientation == 'h' else self.ylimit 299 | 300 | bar = go.Bar(x=x, y=y, width=self.bar_size, textposition=self.bar_textposition, 301 | texttemplate=self.bar_texttemplate, orientation=self.orientation, 302 | marker_color=colors, insidetextfont=self.bar_label_font, 303 | cliponaxis=False, outsidetextfont=self.bar_label_font, 304 | hovertemplate=self.hovertemplate, **self.bar_kwargs) 305 | 306 | data = [bar] 307 | xaxis, yaxis = (value_axis, label_axis) if self.orientation == 'h' \ 308 | else (label_axis, value_axis) 309 | 310 | annotations = self.get_annotations(i) 311 | if self.slider and i % self.steps_per_period == 0: 312 | slider_steps.append( 313 | {"args": [[i], 314 | {"frame": {"duration": self.duration, "redraw": False}, 315 | "mode": "immediate", 316 | "fromcurrent": True, 317 | "transition": {"duration": self.duration} 318 | }], 319 | "label": self.get_period_label_text(i), 320 | "method": "animate"}) 321 | layout = go.Layout(xaxis=xaxis, yaxis=yaxis, annotations=annotations, 322 | margin={'l': 150}, **self.layout_kwargs) 323 | if self.perpendicular_bar_func: 324 | pbar = self.get_perpendicular_bar(bar_vals, i, layout) 325 | layout.update(shapes=[pbar], overwrite=True) 326 | frames.append(go.Frame(data=data, layout=layout, name=i)) 327 | 328 | return frames, slider_steps 329 | 330 | def get_period_label_text(self, i): 331 | if self.period_template: 332 | idx_val = self.df_values.index[i] 333 | if self.df_values.index.dtype.kind == 'M': 334 | s = idx_val.strftime(self.period_template) 335 | else: 336 | s = self.period_template.format(x=idx_val) 337 | else: 338 | s = self.str_index[i] 339 | return s 340 | 341 | def get_annotations(self, i): 342 | annotations = [] 343 | if self.period_label: 344 | self.period_label['text'] = self.get_period_label_text(i) 345 | annotations.append(self.period_label) 346 | 347 | if self.period_summary_func: 348 | values = self.df_values.iloc[i] 349 | ranks = self.df_ranks.iloc[i] 350 | text_dict = self.period_summary_func(values, ranks) 351 | if 'x' not in text_dict or 'y' not in text_dict or 'text' not in text_dict: 352 | name = self.period_summary_func.__name__ 353 | raise ValueError(f'The dictionary returned from `{name}` must contain ' 354 | '"x", "y", and "s"') 355 | text, x, y = text_dict['text'], text_dict['x'], text_dict['y'] 356 | annotations.append(dict(text=text, x=x, y=y, font=dict(size=14), 357 | xref="paper", yref="paper", showarrow=False)) 358 | 359 | return annotations 360 | 361 | def get_perpendicular_bar(self, bar_vals, i, layout): 362 | if isinstance(self.perpendicular_bar_func, str): 363 | val = pd.Series(bar_vals).agg(self.perpendicular_bar_func) 364 | else: 365 | values = self.df_values.iloc[i] 366 | ranks = self.df_ranks.iloc[i] 367 | val = self.perpendicular_bar_func(values, ranks) 368 | 369 | xref, yref = ("x", "paper") if self.orientation == 'h' else ("paper", "y") 370 | value_limit = self.xlimit if self.orientation == 'h' else self.ylimit 371 | if self.fixed_max: 372 | delta = (value_limit[1] - value_limit[0]) * .02 373 | else: 374 | delta = (1.05 * bar_vals.max() - bar_vals.min()) * .02 375 | 376 | x0, x1 = (val - delta, val + delta) if self.orientation == 'h' else (0, 1) 377 | y0, y1 = (val - delta, val + delta) if self.orientation == 'v' else (0, 1) 378 | 379 | return dict(type="rect", xref=xref, yref=yref, x0=x0, y0=y0, x1=x1, y1=y1, 380 | fillcolor="#444444",layer="below", opacity=.5, line_width=0) 381 | 382 | def make_animation(self): 383 | frames, slider_steps = self.get_frames() 384 | data = frames[0].data 385 | layout = frames[0].layout 386 | layout.title = self.title 387 | layout.updatemenus = [dict( 388 | type="buttons", 389 | direction = "left", 390 | x=1, 391 | y=1.02, 392 | xanchor='right', 393 | yanchor='bottom', 394 | buttons=[dict(label="Play", 395 | method="animate", 396 | # redraw must be true for bar plots 397 | args=[None, {"frame": {"duration": self.duration, "redraw": True}, 398 | "fromcurrent": True 399 | }]), 400 | dict(label="Pause", 401 | method="animate", 402 | args=[[None], {"frame": {"duration": 0, "redraw": False}, 403 | "mode": "immediate", 404 | "transition": {"duration": 0}}]), 405 | ] 406 | )] 407 | 408 | sliders_dict = { 409 | "active": 0, 410 | "yanchor": "top", 411 | "xanchor": "left", 412 | "currentvalue": { 413 | # "font": {"size": 20}, 414 | # "prefix": '', # allow user to set 415 | "visible": False, # just repeats period label 416 | # "xanchor": "right" 417 | }, 418 | "transition": {"duration": self.duration, "easing": "cubic-in-out"}, 419 | "pad": {"b": 10, "t": 50}, 420 | "len": 0.88, 421 | "x": 0.05, 422 | "y": 0, 423 | "steps": slider_steps 424 | } 425 | if self.slider: 426 | layout.sliders = [sliders_dict] 427 | 428 | fig = go.Figure(data=data, layout=layout, frames=frames[1:]) 429 | if self.filename: 430 | fig.write_html(self.filename, **self.write_html_kwargs) 431 | else: 432 | return fig 433 | 434 | 435 | def bar_chart_race_plotly(df, filename=None, orientation='h', sort='desc', n_bars=None, 436 | fixed_order=False, fixed_max=False, steps_per_period=10, 437 | period_length=500, end_period_pause=0, interpolate_period=False, 438 | period_label=True, period_template=None, period_summary_func=None, 439 | perpendicular_bar_func=None, colors=None, title=None, bar_size=.95, 440 | bar_textposition='outside', bar_texttemplate=None, bar_label_font=None, 441 | tick_label_font=None, hovertemplate=None, slider=True, scale='linear', 442 | bar_kwargs=None, layout_kwargs=None, write_html_kwargs=None, 443 | filter_column_colors=False): 444 | ''' 445 | Create an animated bar chart race using Plotly. Data must be in 446 | 'wide' format where each row represents a single time period and each 447 | column represents a distinct category. Optionally, the index can label 448 | the time period. Bar length and location change linearly from one time 449 | period to the next. 450 | 451 | Note - The duration of each frame is calculated as 452 | `period_length` / `steps_per_period`, but is unlikely to actually 453 | be this number, especially when duration is low (< 50ms). You may have to 454 | experiment with different combinations of `period_length` and 455 | `steps_per_period` to get the animation at the desired speed. 456 | 457 | If no `filename` is given, a plotly figure is returned that is embedded 458 | into the notebook. 459 | 460 | Parameters 461 | ---------- 462 | df : pandas DataFrame 463 | Must be a 'wide' DataFrame where each row represents a single period 464 | of time. Each column contains the values of the bars for that 465 | category. Optionally, use the index to label each time period. 466 | The index can be of any type. 467 | 468 | filename : `None` or str, default None 469 | If `None` return plotly animation, otherwise save 470 | to disk. Can only save as HTML at this time. 471 | 472 | orientation : 'h' or 'v', default 'h' 473 | Bar orientation - horizontal or vertical 474 | 475 | sort : 'desc' or 'asc', default 'desc' 476 | Choose how to sort the bars. Use 'desc' to put largest bars on top 477 | and 'asc' to place largest bars on bottom. 478 | 479 | n_bars : int, default None 480 | Choose the maximum number of bars to display on the graph. 481 | By default, use all bars. New bars entering the race will appear 482 | from the edge of the axes. 483 | 484 | fixed_order : bool or list, default False 485 | When `False`, bar order changes every time period to correspond 486 | with `sort`. When `True`, bars remained fixed according to their 487 | final value corresponding with `sort`. Otherwise, provide a list 488 | of the exact order of the categories for the entire duration. 489 | 490 | fixed_max : bool, default False 491 | Whether to fix the maximum value of the axis containing the values. 492 | When `False`, the axis for the values will have its maximum (x/y) 493 | just after the largest bar of the current time period. 494 | The axis maximum will change along with the data. 495 | 496 | When True, the maximum axis value will remain constant for the 497 | duration of the animation. For example, in a horizontal bar chart, 498 | if the largest bar has a value of 100 for the first time period and 499 | 10,000 for the last time period. The xlim maximum will be 10,000 500 | for each frame. 501 | 502 | steps_per_period : int, default 10 503 | The number of steps to go from one time period to the next. 504 | The bars will grow linearly between each period. 505 | 506 | period_length : int, default 500 507 | Number of milliseconds to animate each period (row). 508 | Default is 500ms (half of a second) 509 | 510 | end_period_pause : int, default 0 511 | Number of milliseconds to pause the animation at the end of 512 | each period. 513 | 514 | interpolate_period : bool, default `False` 515 | Whether to interpolate the period. Only valid for datetime or 516 | numeric indexes. When set to `True`, for example, 517 | the two consecutive periods 2020-03-29 and 2020-03-30 with 518 | `steps_per_period` set to 4 would yield a new index of 519 | 2020-03-29 00:00:00 520 | 2020-03-29 06:00:00 521 | 2020-03-29 12:00:00 522 | 2020-03-29 18:00:00 523 | 2020-03-30 00:00:00 524 | 525 | period_label : bool or dict, default `True` 526 | If `True` or dict, use the index as a large text label 527 | on the figure labeling each period. No label when 'False'. 528 | 529 | Use a dictionary to supply the exact position of the period 530 | along with any valid parameters of a plotly annotation. 531 | 532 | Example: 533 | { 534 | 'x': .99, 535 | 'y': .8, 536 | 'font' : {'family': 'Helvetica', 'size': 20, 'color': 'orange'}, 537 | 'xanchor': 'right', 538 | } 539 | 540 | Reference - https://plotly.com/python/reference/#layout-annotations 541 | 542 | The default location depends on `orientation` and `sort` 543 | * h, desc -> x=.95, y=.15 544 | * h, asc -> x=.95, y=.85 545 | * v, desc -> x=.95, y=.85 546 | * v, asc -> x=.05, y=.85 547 | 548 | period_template : str, default `None` 549 | Either a string with date directives or 550 | a new-style (Python 3.6+) formatted string 551 | 552 | For a string with a date directive, find the complete list here 553 | https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes 554 | 555 | Example of string with date directives 556 | '%B %d, %Y' 557 | Will change 2020/03/29 to March 29, 2020 558 | 559 | For new-style formatted string. Use curly braces and the variable `x`, 560 | which will be passed the current period's index value. 561 | Example: 562 | 'Period {x:10.2f}' 563 | 564 | Date directives will only be used for datetime indexes. 565 | 566 | period_summary_func : function, default None 567 | Custom text added to the axes each period. 568 | Create a user-defined function that accepts two pandas Series of the 569 | current time period's values and ranks. It must return a dictionary 570 | containing at a minimum the keys "x", "y", and "text" which will be 571 | passed used for a plotly annotation. 572 | 573 | Example: 574 | def func(values, ranks): 575 | total = values.sum() 576 | text = f'Worldwide deaths: {total}' 577 | return {'x': .85, 'y': .2, 'text': text, 'size': 11} 578 | 579 | perpendicular_bar_func : function or str, default None 580 | Creates a single bar perpendicular to the main bars that spans the 581 | length of the axis. 582 | 583 | Use either a string that the DataFrame `agg` method understands or a 584 | user-defined function. 585 | 586 | DataFrame strings - 'mean', 'median', 'max', 'min', etc.. 587 | 588 | The function is passed two pandas Series of the current time period's 589 | data and ranks. It must return a single value. 590 | 591 | def func(values, ranks): 592 | return values.quantile(.75) 593 | 594 | colors : str or sequence colors, default 'dark12' 595 | Colors to be used for the bars. All matplotlib and plotly colormaps are 596 | available by string name. Colors will repeat if there are more bars than colors. 597 | 598 | 'dark12' is the default colormap. If there are more than 10 columns, 599 | then the default colormap will be 'dark24' 600 | 601 | Append "_r" to the colormap name to use the reverse of the colormap. 602 | i.e. "dark12_r" 603 | 604 | title : str, dict, or plotly.graph_objects.layout.Title , default None 605 | Title of animation. Use a string for simple titles or a 606 | dictionary to specify several properties 607 | {'text': 'My Bar Chart Race', 608 | 'x':0.5, 609 | 'y':.9, 610 | 'xanchor': 'center', 611 | 'yanchor': 'bottom'} 612 | 613 | Other properties include: font, pad, xref, yref 614 | 615 | bar_size : float, default .95 616 | Height/width of bars for horizontal/vertical bar charts. 617 | Use a number between 0 and 1 618 | Represents the fraction of space that each bar takes up. 619 | When equal to 1, no gap remains between the bars. 620 | 621 | bar_textposition : str or sequence, default `None` 622 | Position on bar to place its label. 623 | Use one of the strings - 'inside', 'outside', 'auto', 'none' 624 | or a sequence of the above 625 | 626 | bar_texttemplate : str, default '%{x:,.0f}' or '%{y:,.0f}' 627 | Template string used for rendering the text inside/outside 628 | the bars. Variables are inserted using %{variable}, 629 | for example "y: %{y}". Numbers are formatted using 630 | d3-format's syntax %{variable:d3-format}, for example 631 | "Price: %{y:$.2f}". 632 | 633 | bar_label_font : number or dict, None 634 | Font size of numeric bar labels. When None, font size is 12. 635 | Use a dictionary to supply several font properties. 636 | Example: 637 | { 638 | 'size': 12, 639 | 'family': 'Courier New, monospace', 640 | 'color': '#7f7f7f' 641 | } 642 | 643 | tick_label_font : number or dict, None 644 | Font size of tick labels.When None, font size is 12. 645 | Use a dictionary to supply several font properties. 646 | 647 | hovertemplate : str, default None 648 | Template string used for rendering the information that appear 649 | on hover box. By default, it is '%{y} - %{x:,.0f}' 650 | 651 | Reference: https://plotly.com/python/hover-text-and-formatting 652 | 653 | slider : bool, default True 654 | Whether or not to place a slider below the animation 655 | 656 | scale : 'linear' or 'log', default 'linear' 657 | Type of scaling to use for the axis containing the values 658 | 659 | bar_kwargs : dict, default `None` (opacity=.8) 660 | Other keyword arguments (within a dictionary) forwarded to the 661 | plotly `go.Bar` function. If no value for 'opacity' is given, 662 | then it is set to .8 by default. 663 | 664 | layout_kwargs : dict or go.Layout instance, default None 665 | Other keyword arguments (within a dictionary) are forwarded to 666 | the plotly `go.Layout` function. Use this to control the size of 667 | the figure. 668 | Example: 669 | { 670 | 'width': 600, 671 | 'height': 400, 672 | 'showlegend': True 673 | } 674 | 675 | write_html_kwargs : dict, default None 676 | Arguments passed to the write_html plotly go.Figure method. 677 | Example: 678 | { 679 | 'auto_play': False, 680 | 'include_plotlyjs': 'cdn', 681 | 'full_html': False= 682 | } 683 | Reference: https://plotly.github.io/plotly.py-docs/generated/plotly.io.write_html.html 684 | 685 | filter_column_colors : bool, default `False` 686 | When setting n_bars, it's possible that some columns never 687 | appear in the animation. Regardless, all columns get assigned 688 | a color by default. 689 | 690 | For instance, suppose you have 100 columns 691 | in your DataFrame, set n_bars to 10, and 15 different columns 692 | make at least one appearance in the animation. Even if your 693 | colormap has at least 15 colors, it's possible that many 694 | bars will be the same color, since each of the 100 columns is 695 | assigned of the colormaps colors. 696 | 697 | Setting this to `True` will map your colormap to just those 698 | columns that make an appearance in the animation, helping 699 | avoid duplication of colors. 700 | 701 | Setting this to `True` will also have the (possibly unintended) 702 | consequence of changing the colors of each color every time a 703 | new integer for n_bars is used. 704 | 705 | EXPERIMENTAL 706 | This parameter is experimental and may be changed/removed 707 | in a later version. 708 | 709 | Returns 710 | ------- 711 | When `filename` is left as `None`, a plotly figure is returned and 712 | embedded into the notebook. Otherwise, a file of the HTML is 713 | saved and `None` is returned. 714 | 715 | References 716 | ----- 717 | Plotly Figure - https://plotly.com/python/reference 718 | Plotly API - https://plotly.com/python-api-reference 719 | d3 formatting - https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md 720 | 721 | Examples 722 | -------- 723 | Use the `load_data` function to get an example dataset to 724 | create an animation. 725 | 726 | df = bcr.load_dataset('covid19') 727 | bcr.bar_chart_race_plotly( 728 | df=df, 729 | filename='covid19_horiz_desc.html', 730 | orientation='h', 731 | sort='desc', 732 | n_bars=8, 733 | fixed_order=False, 734 | fixed_max=True, 735 | steps_per_period=10, 736 | period_length=500, 737 | interpolate_period=False, 738 | period_label={'x': .99, 'y': .8, 'font': {'size': 25, 'color': 'blue'}}, 739 | period_template='%B %d, %Y', 740 | period_summary_func=lambda v, r: {'x': .85, 'y': .2, 741 | 's': f'Total deaths: {v.sum()}', 742 | 'size': 11}, 743 | perpendicular_bar_func='median', 744 | colors='dark12', 745 | title='COVID-19 Deaths by Country', 746 | bar_size=.95, 747 | bar_textposition='outside', 748 | bar_texttemplate='%{x}', 749 | bar_label_font=12, 750 | tick_label_font=12, 751 | hovertemplate=None, 752 | scale='linear', 753 | bar_kwargs={'opacity': .7}, 754 | write_html_kwargs=None, 755 | filter_column_colors=False) 756 | ''' 757 | bcr = _BarChartRace(df, filename, orientation, sort, n_bars, fixed_order, fixed_max, 758 | steps_per_period, period_length, end_period_pause, interpolate_period, 759 | period_label, period_template, period_summary_func, perpendicular_bar_func, 760 | colors, title, bar_size, bar_textposition, bar_texttemplate, bar_label_font, 761 | tick_label_font, hovertemplate, slider, scale, bar_kwargs, layout_kwargs, 762 | write_html_kwargs, filter_column_colors) 763 | return bcr.make_animation() 764 | -------------------------------------------------------------------------------- /bar_chart_race/_codes/code_value.csv: -------------------------------------------------------------------------------- 1 | code,value 2 | country,https://github.com/hjnilsson/country-flags/raw/master/png250px/{code}.png 3 | nfl,self -------------------------------------------------------------------------------- /bar_chart_race/_codes/country.csv: -------------------------------------------------------------------------------- 1 | code,value 2 | ad,ad 3 | and,ad 4 | andorra,ad 5 | principality of andorra,ad 6 | principality of the valleys of andorra,ad 7 | principat d'andorra,ad 8 | ae,ae 9 | al emirat al arabbiya al muttahida,ae 10 | are,ae 11 | the emirates,ae 12 | trucial states,ae 13 | u.a.e,ae 14 | uae,ae 15 | united arab emirates,ae 16 | af,af 17 | afghanistan,af 18 | da afġānistān islāmī jumhoryat,af 19 | islamic republic of afghanistan,af 20 | jomhūrīyyeh eslāmīyyeh afġānestān,af 21 | ag,ag 22 | antigua and barbuda,ag 23 | atg,ag 24 | ai,ai 25 | aia,ai 26 | anguilla,ai 27 | al,al 28 | alb,al 29 | albania,al 30 | arbanon,al 31 | arnavutluk,al 32 | republic of albania,al 33 | republika e shqipërisë,al 34 | am,am 35 | arm,am 36 | armenia,am 37 | hayastan,am 38 | republic of armenia,am 39 | ago,ao 40 | angola,ao 41 | ao,ao 42 | antarctica,aq 43 | aq,aq 44 | ata,aq 45 | ar,ar 46 | arg,ar 47 | argentina,ar 48 | argentine confederation,ar 49 | argentine nation,ar 50 | argentine republic,ar 51 | la argentina,ar 52 | the argentine,ar 53 | united provinces of the río de la plata,ar 54 | american samoa,as 55 | as,as 56 | asm,as 57 | at,at 58 | austria,at 59 | aut,at 60 | republic of austria,at 61 | republik österreich,at 62 | österreich,at 63 | au,au 64 | aus,au 65 | australia,au 66 | commonwealth of australia,au 67 | new holland,au 68 | abw,aw 69 | aruba,aw 70 | aw,aw 71 | ala,ax 72 | ax,ax 73 | åland islands,ax 74 | åland,ax 75 | aland,ax 76 | aland islands,ax 77 | az,az 78 | aze,az 79 | azerbaijan,az 80 | azərbaycan respublikası,az 81 | republic of azerbaijan,az 82 | ba,ba 83 | bih,ba 84 | bosnia and herzegovina,ba 85 | republic of bosnia and herzegovina,ba 86 | socialist republic of bosnia and herzegovina,ba 87 | barbados,bb 88 | bb,bb 89 | bimshire,bb 90 | brb,bb 91 | bangladesh,bd 92 | bd,bd 93 | bengal presidency,bd 94 | bgd,bd 95 | east bengal,bd 96 | east pakistan,bd 97 | people's republic of bangladesh,bd 98 | be,be 99 | bel,be 100 | belgium,be 101 | kingdom of belgium,be 102 | koninkrijk belgië,be 103 | königreich belgien,be 104 | royaume de belgique,be 105 | bf,bf 106 | bfa,bf 107 | bourkina-fasso,bf 108 | burkina faso,bf 109 | haute-volta,bf 110 | upper volta,bf 111 | bg,bg 112 | bgr,bg 113 | bulgaria,bg 114 | people's republic of bulgaria,bg 115 | republic of bulgaria,bg 116 | republika bǎlgariya,bg 117 | bahrain,bh 118 | bh,bh 119 | bhr,bh 120 | kingdom of bahrain,bh 121 | mamlakat al-baḥrayn,bh 122 | state of bahrain,bh 123 | bdi,bi 124 | bi,bi 125 | burundi,bi 126 | kingdom of burundi,bi 127 | republic of burundi,bi 128 | republika y'uburundi,bi 129 | république du burundi,bi 130 | ben,bj 131 | benin,bj 132 | bj,bj 133 | dahomey,bj 134 | republic of benin,bj 135 | république du bénin,bj 136 | blm,bl 137 | saint barthélemy,bl 138 | bl,bl 139 | bermuda,bm 140 | bm,bm 141 | bmu,bm 142 | bn,bn 143 | brn,bn 144 | brunei,bn 145 | brunei darussalam,bn 146 | negara brunei darussalam,bn 147 | state of brunei,bn 148 | نڬارا بروني دارالسلام,bn 149 | bo,bo 150 | bol,bo 151 | bolivia,bo 152 | estado plurinacional de bolivia,bo 153 | plurinational state of bolivia,bo 154 | republic of bolivia,bo 155 | republic of bolívar,bo 156 | bes,bq 157 | bq,bq 158 | sint eustatius,bq 159 | br,br 160 | bra,br 161 | brazil,br 162 | empire of brazil,br 163 | federative republic of brazil,br 164 | ilha de vera cruz,br 165 | pindorama,br 166 | república federativa do brasil,br 167 | terra de santa cruz,br 168 | terra di papaga,br 169 | united states of brazil,br 170 | bahamas,bs 171 | bhs,bs 172 | bs,bs 173 | commonwealth of the bahamas,bs 174 | the bahamas,bs 175 | bhutan,bt 176 | bt,bt 177 | btn,bt 178 | druk gyal khap,bt 179 | kingdom of bhutan,bt 180 | bouvet island,bv 181 | bv,bv 182 | bvt,bv 183 | bechuanaland,bw 184 | botswana,bw 185 | bw,bw 186 | bwa,bw 187 | republic of botswana,bw 188 | belarus,by 189 | blr,by 190 | by,by 191 | byelorussia,by 192 | gudija,by 193 | republic of belarus,by 194 | white russia,by 195 | belize,bz 196 | blz,bz 197 | british honduras,bz 198 | bz,bz 199 | ca,ca 200 | can,ca 201 | canada,ca 202 | dominion du canada,ca 203 | dominion of canada,ca 204 | cck,cc 205 | cc,cc 206 | cocos islands,cc 207 | cocos (keeling) islands,cc 208 | keeling islands,cc 209 | territory of cocos islands,cc 210 | belgian congo,cd 211 | cd,cd 212 | cod,cd 213 | congo belge,cd 214 | congo free state,cd 215 | congo kinshasa,cd 216 | democratic republic of congo,cd 217 | congo (kinshasa),cd 218 | drc,cd 219 | zaire,cd 220 | caf,cf 221 | central african empire,cf 222 | central african republic,cf 223 | cf,cf 224 | empire centrafricain,cf 225 | oubangui-chari,cf 226 | république centrafricaine,cf 227 | ubangi-shari,cf 228 | cg,cg 229 | cog,cg 230 | congo,cg 231 | congo brazzaville,cg 232 | french congo,cg 233 | republic of the congo,cg 234 | congo (brazzaville),cg 235 | the congo,cg 236 | ch,ch 237 | che,ch 238 | confederazione svizzera,ch 239 | confederaziun svizra,ch 240 | confédération suisse,ch 241 | schweiz,ch 242 | schweizerische eidgenossenschaft,ch 243 | suisse,ch 244 | svizra,ch 245 | svizzera,ch 246 | swiss confederation,ch 247 | switzerland,ch 248 | ci,ci 249 | civ,ci 250 | côte d'ivoire,ci 251 | ivory coast,ci 252 | cote d'ivoire,ci 253 | republic of côte d'ivoire,ci 254 | république de côte d'ivoire,ci 255 | the ivory coast,ci 256 | cok,ck 257 | ck,ck 258 | cook islands,ck 259 | capitania general de chile,cl 260 | chile,cl 261 | chili,cl 262 | chilli,cl 263 | chl,cl 264 | cl,cl 265 | republic of chile,cl 266 | reyno de chile,cl 267 | cameroon,cm 268 | cm,cm 269 | cmr,cm 270 | kamerun,cm 271 | republic of cameroon,cm 272 | united republic of cameroon,cm 273 | china,cn 274 | chn,cn 275 | cn,cn 276 | communist china,cn 277 | people's republic of china,cn 278 | red china,cn 279 | co,co 280 | col,co 281 | colombia,co 282 | confederación granadina,co 283 | estados unidos de colombia,co 284 | republic of colombia,co 285 | república de colombia,co 286 | república de la nueva granada,co 287 | costa rica,cr 288 | cr,cr 289 | cri,cr 290 | republic of costa rica,cr 291 | república de costa rica,cr 292 | cu,cu 293 | cub,cu 294 | cuba,cu 295 | republic of cuba,cu 296 | república de cuba,cu 297 | cabo verde,cv 298 | cape verde,cv 299 | cpv,cv 300 | cv,cv 301 | curaçao,cw 302 | cuw,cw 303 | cw,cw 304 | christmas island,cx 305 | cx,cx 306 | cxr,cx 307 | cy,cy 308 | cyp,cy 309 | cyprus,cy 310 | bohemia,cz 311 | cz,cz 312 | cze,cz 313 | czech republic,cz 314 | czechia,cz 315 | czechland,cz 316 | koruna česká,cz 317 | the czechlands,cz 318 | čechy a morava,cz 319 | česko,cz 320 | česká konfederace,cz 321 | česká republika,cz 322 | alemania,de 323 | allemagne,de 324 | bundesrepublik deutschland,de 325 | de,de 326 | deu,de 327 | deutschland,de 328 | federal republic of germany,de 329 | german democratic republic,de 330 | germania,de 331 | germany,de 332 | dj,dj 333 | dji,dj 334 | djibouti,dj 335 | republic of djibouti,dj 336 | république de djibouti,dj 337 | danmark,dk 338 | denmark,dk 339 | dk,dk 340 | dnk,dk 341 | kingdom of denmark,dk 342 | kongeriget danmark,dk 343 | dm,dm 344 | dma,dm 345 | dominica,dm 346 | do,do 347 | dom,do 348 | dominican republic,do 349 | al-jazā’ir,dz 350 | algeria,dz 351 | dz,dz 352 | dza,dz 353 | people's democratic republic of algeria,dz 354 | ec,ec 355 | ecu,ec 356 | ecuador,ec 357 | ee,ee 358 | eesti,ee 359 | est,ee 360 | estland,ee 361 | estonia,ee 362 | aegyptus,eg 363 | arab republic of egypt,eg 364 | eg,eg 365 | egy,eg 366 | egypt,eg 367 | esh,eh 368 | western sahara,eh 369 | the sahrawi arab democratic republic,eh 370 | er,er 371 | eri,er 372 | eritrea,er 373 | es,es 374 | esp,es 375 | espainia,es 376 | espanya,es 377 | españa,es 378 | kingdom of spain,es 379 | spain,es 380 | spanish state,es 381 | abyssinia,et 382 | al-habasha,et 383 | et,et 384 | eth,et 385 | ethiopia,et 386 | ethiopië,et 387 | federal democratic republic of ethiopia,et 388 | habeshastan,et 389 | fi,fi 390 | fin,fi 391 | finland,fi 392 | republic of finland,fi 393 | republiken finland,fi 394 | soome,fi 395 | suomen tasavalta,fi 396 | suomenmaa,fi 397 | suomi,fi 398 | suomâ and suomâ täsiväldi,fi 399 | suopma and suoma dásseváldi,fi 400 | fiji,fj 401 | fj,fj 402 | fji,fj 403 | falkland islands,fk 404 | fk,fk 405 | flk,fk 406 | fm,fm 407 | fsm,fm 408 | micronesia,fm 409 | fro,fo 410 | fo,fo 411 | faroe,fo 412 | faroe islands,fo 413 | the faroe,fo 414 | the faroe islands,fo 415 | farança,fr 416 | fr,fr 417 | fra,fr 418 | france,fr 419 | french republic,fr 420 | gaul,fr 421 | gaule,fr 422 | l'hexagone,fr 423 | république française,fr 424 | tsarfat,fr 425 | γαλλία,fr 426 | ga,ga 427 | gab,ga 428 | gabon,ga 429 | gabonese republic,ga 430 | anglia,gb 431 | britain,gb 432 | britannia,gb 433 | caledonia,gb 434 | gb,gb 435 | gbr,gb 436 | great britain,gb 437 | u.k.,gb 438 | uk,gb 439 | united kingdom,gb 440 | united kingdom of great britain and northern ireland,gb 441 | gd,gd 442 | grd,gd 443 | grenada,gd 444 | colchis,ge 445 | ge,ge 446 | geo,ge 447 | georgia,ge 448 | iveria,ge 449 | republic of georgia,ge 450 | گرجستان,ge 451 | french guiana,gf 452 | gf,gf 453 | guf,gf 454 | gg,gg 455 | ggy,gg 456 | guernsey,gg 457 | gaana,gh 458 | gh,gh 459 | gha,gh 460 | ghana,gh 461 | ghana tiŋzuɣu,gh 462 | gold coast,gh 463 | united gold coast convention,gh 464 | gi,gi 465 | gib,gi 466 | gibraltar,gi 467 | gl,gl 468 | greenland,gl 469 | grl,gl 470 | gm,gm 471 | gmb,gm 472 | the gambia,gm 473 | gambia,gm 474 | gambia (the),gm 475 | the republic of gambia,gm 476 | gin,gn 477 | gn,gn 478 | guinea,gn 479 | glp,gp 480 | gp,gp 481 | guadeloupe,gp 482 | equatorial guinea,gq 483 | gnq,gq 484 | gq,gq 485 | gr,gr 486 | grc,gr 487 | greece,gr 488 | hellas,gr 489 | hellenic republic,gr 490 | yavan,gr 491 | yunanistan,gr 492 | ελλάδα,gr 493 | ελλάς,gr 494 | sgs,gs 495 | gs,gs 496 | south georgia and the south sandwich islands,gs 497 | gt,gt 498 | gtm,gt 499 | guatemala,gt 500 | gu,gu 501 | guam,gu 502 | gum,gu 503 | gnb,gw 504 | guinea-bissau,gw 505 | guiné-bissao,gw 506 | gw,gw 507 | portuguese guinea,gw 508 | republic of guinea-bissau,gw 509 | guy,gy 510 | guyana,gy 511 | gy,gy 512 | hk,hk 513 | hkg,hk 514 | hong kong,hk 515 | hmd,hm 516 | hm,hm 517 | heard island and mcdonald islands,hm 518 | territory of heard island and mcdonald islands,hm 519 | hn,hn 520 | hnd,hn 521 | honduras,hn 522 | croatia,hr 523 | hr,hr 524 | hrv,hr 525 | hrvatska,hr 526 | hrvaška,hr 527 | republic of croatia,hr 528 | republika hrvatska,hr 529 | haiti,ht 530 | ht,ht 531 | hti,ht 532 | hu,hu 533 | hun,hu 534 | hungaria,hu 535 | hungarian people's republic,hu 536 | hungary,hu 537 | kingdom of hungary,hu 538 | magyar királyság,hu 539 | magyar köztársaság,hu 540 | magyar népköztársaság,hu 541 | magyar tanácsköztársaság,hu 542 | magyarország,hu 543 | regnum hungariæ,hu 544 | republic of hungary,hu 545 | hindia belanda,id 546 | id,id 547 | idn,id 548 | indonesia,id 549 | indonesië,id 550 | indunesia,id 551 | insulinde,id 552 | nederlands,id 553 | nusantara,id 554 | republic of indonesia,id 555 | republik indonesia,id 556 | hibernia,ie 557 | ie,ie 558 | ireland,ie 559 | irish free state,ie 560 | irl,ie 561 | republic of ireland,ie 562 | herzlstan,il 563 | il,il 564 | isr,il 565 | israel,il 566 | state of israel,il 567 | yehuda,il 568 | imn,im 569 | im,im 570 | isle of man,im 571 | hindustan,in 572 | in,in 573 | ind,in 574 | india,in 575 | republic of india,in 576 | union of india,in 577 | भारत,in 578 | भारतवर्ष,in 579 | iot,io 580 | io,io 581 | british indian ocean territory,io 582 | assyria,iq 583 | babylon,iq 584 | iq,iq 585 | iraq,iq 586 | irq,iq 587 | mesopotamia,iq 588 | ir,ir 589 | iran,ir 590 | irn,ir 591 | islamic republic of iran,ir 592 | persia,ir 593 | iceland,is 594 | is,is 595 | isl,is 596 | lýðveldið ísland,is 597 | republic of iceland,is 598 | it,it 599 | ita,it 600 | italia,it 601 | italy,it 602 | repubblica italiana,it 603 | the beautiful country,it 604 | the boot,it 605 | je,je 606 | jersey,je 607 | jey,je 608 | jam,jm 609 | jamaica,jm 610 | jm,jm 611 | xamayca,jm 612 | hashemite kingdom of jordan,jo 613 | jo,jo 614 | jor,jo 615 | jordan,jo 616 | japan,jp 617 | jp,jp 618 | jpn,jp 619 | nihon,jp 620 | nippon,jp 621 | yamato,jp 622 | ōyashima,jp 623 | british east africa protectorate,ke 624 | jamhuri ya kenya,ke 625 | ke,ke 626 | ken,ke 627 | kenya,ke 628 | kenya colony,ke 629 | republic of kenya,ke 630 | kg,kg 631 | kgz,kg 632 | kirghiz soviet socialist republic,kg 633 | kirghizia,kg 634 | kirgizstan,kg 635 | kyrgyz republic,kg 636 | kyrgyzskaya respublika,kg 637 | kyrgyzstan,kg 638 | republic of kyrgyzstan,kg 639 | кыргызстан,kg 640 | cambodia,kh 641 | democratic kampuchea,kh 642 | kampuchea,kh 643 | kh,kh 644 | khm,kh 645 | khmer republic,kh 646 | kingdom of cambodia,kh 647 | people's republic of kampuchea,kh 648 | royaume du cambodge,kh 649 | state of cambodia,kh 650 | ki,ki 651 | kir,ki 652 | kiribati,ki 653 | al-ittiḥād al-qumurī,km 654 | com,km 655 | comoros,km 656 | km,km 657 | udzima wa komori,km 658 | union des comores,km 659 | union of the comoros,km 660 | united republic of the commoros,km 661 | federation of saint christopher and nevis,kn 662 | federation of saint kitts and nevis,kn 663 | kn,kn 664 | kna,kn 665 | liamuiga and oualie,kn 666 | saint christopher and nevis,kn 667 | saint christopher-nevis-anguilla,kn 668 | saint kitts and nevis,kn 669 | saint kitts-nevis-anguilla,kn 670 | cho-son,kp 671 | choson minjujuui inmin konghwaguk,kp 672 | d.p.r.k.,kp 673 | democratic people's republic of korea,kp 674 | dprk,kp 675 | kp,kp 676 | north korea,kp 677 | prk,kp 678 | dae-han-min-guk,kr 679 | han-guk,kr 680 | kor,kr 681 | korea,kr 682 | korea republic,kr 683 | kr,kr 684 | republic of korea,kr 685 | south korea,kr 686 | "korea, south",kr 687 | kuwait,kw 688 | kw,kw 689 | kwt,kw 690 | cayman islands,ky 691 | cym,ky 692 | ky,ky 693 | kaz,kz 694 | kazakh soviet socialist republic,kz 695 | kazakhstan,kz 696 | kz,kz 697 | qazaqstan,kz 698 | republic of kazakhstan,kz 699 | қазақстан,kz 700 | қазақстан республикасы,kz 701 | la,la 702 | lao,la 703 | lao people's democratic republic,la 704 | laos,la 705 | république démocratique populaire lao,la 706 | sathalanalat paxathipatai paxaxôn,la 707 | ສາທາລະນະລັດ ປະຊາທິປະໄຕ ປະຊາຊົນລາວ,la 708 | al-jumhuriyya al-lubnaniyya,lb 709 | lb,lb 710 | lbn,lb 711 | lebanon,lb 712 | lebnan,lb 713 | levanon,lb 714 | liban,lb 715 | lubnan,lb 716 | the lebanese republic,lb 717 | lc,lc 718 | lca,lc 719 | saint lucia,lc 720 | li,li 721 | lie,li 722 | liechtenstein,li 723 | ceylon,lk 724 | democratic socialist republic of sri lanka,lk 725 | heladiva,lk 726 | lk,lk 727 | lka,lk 728 | ratnadeepa,lk 729 | sri lanka,lk 730 | taprobane,lk 731 | lbr,lr 732 | liberia,lr 733 | lr,lr 734 | basutoland,ls 735 | kingdom of lesotho,ls 736 | lesotho,ls 737 | ls,ls 738 | lso,ls 739 | lietuva,lt 740 | lietuvos respublika,lt 741 | lita,lt 742 | lithuania,lt 743 | lt,lt 744 | ltu,lt 745 | литва,lt 746 | grand duchy of luxembourg,lu 747 | grand-duché de luxembourg,lu 748 | groussherzogdem lëtzebuerg,lu 749 | großherzogtum luxemburg,lu 750 | lu,lu 751 | lussemburgo,lu 752 | lux,lu 753 | luxembourg,lu 754 | luxemburg,lu 755 | luxemburgo,lu 756 | lëtzebuerg,lu 757 | latvia,lv 758 | latvija,lv 759 | letland,lv 760 | letonija,lv 761 | letonnie,lv 762 | lettland,lv 763 | lv,lv 764 | lva,lv 765 | lby,ly 766 | libya,ly 767 | ly,ly 768 | al mamkaka al maghribiya,ma 769 | kingdom of morocco,ma 770 | ma,ma 771 | mar,ma 772 | morocco,ma 773 | mc,mc 774 | mco,mc 775 | monaco,mc 776 | boğdan,md 777 | md,md 778 | mda,md 779 | moldau,md 780 | moldavia,md 781 | moldavian soviet socialist republic,md 782 | moldova,md 783 | republic of moldova,md 784 | republica moldova,md 785 | me,me 786 | mne,me 787 | montenegro,me 788 | republic of montenegro,me 789 | maf,mf 790 | mf,mf 791 | collectivity of saint martin,mf 792 | saint martin,mf 793 | madagascar,mg 794 | madagasikara,mg 795 | mdg,mg 796 | mg,mg 797 | repoblikan'i madagasikara,mg 798 | republic of madagascar,mg 799 | république de madagascar,mg 800 | mhl,mh 801 | mh,mh 802 | marshall islands,mh 803 | macedonia,mk 804 | mk,mk 805 | mkd,mk 806 | north macedonia,mk 807 | paeonia,mk 808 | republic of north macedonia,mk 809 | vardar banovina,mk 810 | македонија,mk 811 | северна македонија,mk 812 | mali,ml 813 | ml,ml 814 | mli,ml 815 | burma,mm 816 | mm,mm 817 | mmr,mm 818 | myanmar,mm 819 | republic of the union of myanmar,mm 820 | mng,mn 821 | mongolia,mn 822 | the state of mongolia,mn 823 | mac,mo 824 | macao,mo 825 | mo,mo 826 | mnp,mp 827 | mp,mp 828 | northern mariana islands,mp 829 | martinique,mq 830 | mq,mq 831 | mtq,mq 832 | islamic republic of mauritania,mr 833 | mauritania,mr 834 | mr,mr 835 | mrt,mr 836 | république islamique de mauritanie,mr 837 | montserrat,ms 838 | ms,ms 839 | msr,ms 840 | malta,mt 841 | mlt,mt 842 | mt,mt 843 | mauritius,mu 844 | mu,mu 845 | mus,mu 846 | maldives,mv 847 | mdv,mv 848 | mv,mv 849 | republic of the maldives,mv 850 | the maldive islands,mv 851 | malawi,mw 852 | mw,mw 853 | mwi,mw 854 | nyasaland,mw 855 | republic of malawi,mw 856 | aztlán,mx 857 | aztlān,mx 858 | estados unidos mexicanos,mx 859 | mex,mx 860 | mexico,mx 861 | mx,mx 862 | méjico,mx 863 | méxico,mx 864 | república mexicana,mx 865 | united mexican states,mx 866 | federation of malaysia,my 867 | malaysia,my 868 | my,my 869 | mys,my 870 | persekutuan malaysia,my 871 | moz,mz 872 | mozambique,mz 873 | mz,mz 874 | namibia,na 875 | na,na 876 | nam,na 877 | nc,nc 878 | ncl,nc 879 | new caledonia,nc 880 | ne,ne 881 | ner,ne 882 | niger,ne 883 | nf,nf 884 | nfk,nf 885 | norfolk island,nf 886 | ng,ng 887 | nga,ng 888 | nigeria,ng 889 | ni,ni 890 | nic,ni 891 | nicaragua,ni 892 | batavia,nl 893 | holland,nl 894 | kingdom of the netherlands,nl 895 | koninkrijk der nederlanden,nl 896 | nederland,nl 897 | netherlands,nl 898 | nl,nl 899 | nld,nl 900 | pays-bas,nl 901 | kingdom of norway,no 902 | kongeriket noreg,no 903 | kongeriket norge,no 904 | no,no 905 | nor,no 906 | noreg,no 907 | norge,no 908 | norway,no 909 | federal democratic republic of nepal,np 910 | nepal,np 911 | np,np 912 | npl,np 913 | nauru,nr 914 | nr,nr 915 | nru,nr 916 | niu,nu 917 | niue,nu 918 | nu,nu 919 | aotearoa,nz 920 | dominion of new zealand,nz 921 | new zealand,nz 922 | nz,nz 923 | nzl,nz 924 | realm of new zealand,nz 925 | om,om 926 | oman,om 927 | omn,om 928 | sultanate of oman,om 929 | pa,pa 930 | pan,pa 931 | panama,pa 932 | pe,pe 933 | per,pe 934 | peru,pe 935 | peruvian republic,pe 936 | perú,pe 937 | republic of peru,pe 938 | república del perú,pe 939 | república peruana,pe 940 | french polynesia,pf 941 | pf,pf 942 | pyf,pf 943 | papua new guinea,pg 944 | pg,pg 945 | png,pg 946 | filipina,ph 947 | filipinas,ph 948 | haríng bayang katagalugan,ph 949 | islas de san lázaro,ph 950 | las islas felipenas,ph 951 | las islas filipinas,ph 952 | ph,ph 953 | philippine islands,ph 954 | philippines,ph 955 | phl,ph 956 | pilipinas,ph 957 | pinás,ph 958 | republic of the philippines,ph 959 | repúblika ng pilipinas,ph 960 | dominion of pakistan,pk 961 | federation of pakistan,pk 962 | islamic republic of pakistan,pk 963 | mumlikat-e khudadaad pakistan,pk 964 | pak,pk 965 | pakistan,pk 966 | pk,pk 967 | sindhustan and indoscythia,pk 968 | west pakistan,pk 969 | lechia,pl 970 | people's republic of poland,pl 971 | pl,pl 972 | pol,pl 973 | poland,pl 974 | polish-lithuanian commonwealth,pl 975 | polonia,pl 976 | polska,pl 977 | republic of poland,pl 978 | spm,pm 979 | pm,pm 980 | saint pierre and miquelon,pm 981 | pcn,pn 982 | pn,pn 983 | pitcairn,pn 984 | "pitcairn, henderson, ducie and oeno islands",pn 985 | associated free state of puerto rico,pr 986 | commonwealth of puerto rico,pr 987 | estado libre asociado de puerto rico,pr 988 | porto rico,pr 989 | pr,pr 990 | pri,pr 991 | puerto rico,pr 992 | israeli-occupied territories,ps 993 | mandatory palestine,ps 994 | palestine,ps 995 | palestinian national authority,ps 996 | palestinian territories,ps 997 | ps,ps 998 | pse,ps 999 | state of palestine,ps 1000 | west bank and gaza strip,ps 1001 | west bank and gaza,ps 1002 | and galician-portuguese,pt 1003 | and pátria lusitana,pt 1004 | lusitania,pt 1005 | portugal,pt 1006 | portuguese republic,pt 1007 | prt,pt 1008 | pt,pt 1009 | belau,pw 1010 | palau,pw 1011 | plw,pw 1012 | pw,pw 1013 | republic of palau,pw 1014 | paraguay,py 1015 | pry,py 1016 | py,py 1017 | qa,qa 1018 | qat,qa 1019 | qatar,qa 1020 | reu,re 1021 | re,re 1022 | réunion,re 1023 | reunion,re 1024 | kingdom of romania,ro 1025 | regatul româniei,ro 1026 | republica populară romînă,ro 1027 | republica socialistă românia,ro 1028 | ro,ro 1029 | romania,ro 1030 | romanian people's republic,ro 1031 | românia,ro 1032 | rou,ro 1033 | socialist republic of romania,ro 1034 | raška,rs 1035 | republic of serbia,rs 1036 | republika srbija,rs 1037 | rs,rs 1038 | serbia,rs 1039 | serbia and montenegro,rs 1040 | serboslavia,rs 1041 | servia,rs 1042 | srb,rs 1043 | srbija,rs 1044 | yugoslavia,rs 1045 | ru,ru 1046 | rus,ru 1047 | russia,ru 1048 | russian federation,ru 1049 | russian socialist federative soviet republic,ru 1050 | russian soviet federative socialist republic,ru 1051 | soviet union,ru 1052 | union of soviet socialist republics,ru 1053 | ussr,ru 1054 | republic of rwanda,rw 1055 | repubulika y'u rwanda,rw 1056 | ruanda,rw 1057 | rw,rw 1058 | rwa,rw 1059 | rwanda,rw 1060 | rwandese republic,rw 1061 | république du rwanda,rw 1062 | kingdom of saudi arabia,sa 1063 | ksa,sa 1064 | sa,sa 1065 | sau,sa 1066 | saudi arabia,sa 1067 | saudia,sa 1068 | sb,sb 1069 | slb,sb 1070 | solomon islands,sb 1071 | sc,sc 1072 | seychelles,sc 1073 | syc,sc 1074 | anglo-egyptian sudan,sd 1075 | mahdist state,sd 1076 | nubia,sd 1077 | republic of the sudan,sd 1078 | sd,sd 1079 | sdn,sd 1080 | sudan,sd 1081 | the sudan,sd 1082 | kingdom of sweden,se 1083 | konungariket sverige,se 1084 | se,se 1085 | svea rike,se 1086 | sverige,se 1087 | svitjod,se 1088 | swe,se 1089 | sweden,se 1090 | pulau ujong,sg 1091 | republic of singapore,sg 1092 | sg,sg 1093 | sgp,sg 1094 | singapore,sg 1095 | singapura,sg 1096 | sinhapura,sg 1097 | shn,sh 1098 | sh,sh 1099 | saint helena,sh 1100 | "saint helena, ascension and tristan da cunha",sh 1101 | republic of slovenia,si 1102 | republika slovenija,si 1103 | si,si 1104 | slovenia,si 1105 | slovenija,si 1106 | svn,si 1107 | sjm,sj 1108 | sj,sj 1109 | svalbard and jan mayen,sj 1110 | sk,sk 1111 | slovak republic,sk 1112 | slovak socialist republic,sk 1113 | slovakia,sk 1114 | slovensko,sk 1115 | slovenská republika,sk 1116 | slovenská socialistická republika,sk 1117 | svk,sk 1118 | sierra leone,sl 1119 | sl,sl 1120 | sle,sl 1121 | san marino,sm 1122 | sm,sm 1123 | smr,sm 1124 | sen,sn 1125 | senegal,sn 1126 | sn,sn 1127 | so,so 1128 | som,so 1129 | somalia,so 1130 | dutch guiana,sr 1131 | netherlands guiana,sr 1132 | republic of suriname,sr 1133 | republiek suriname,sr 1134 | sr,sr 1135 | sur,sr 1136 | surinam,sr 1137 | suriname,sr 1138 | south sudan,ss 1139 | ss,ss 1140 | ssd,ss 1141 | sao tome and principe,st 1142 | st,st 1143 | stp,st 1144 | el salvador,sv 1145 | slv,sv 1146 | sv,sv 1147 | sxm,sx 1148 | sx,sx 1149 | sint maarten,sx 1150 | sy,sy 1151 | syr,sy 1152 | syria,sy 1153 | eswatini,sz 1154 | kingdom of eswatini,sz 1155 | swaziland,sz 1156 | swz,sz 1157 | sz,sz 1158 | tc,tc 1159 | tca,tc 1160 | turks and caicos,tc 1161 | chad,td 1162 | jumhūrīyat tashād,td 1163 | republic of chad,td 1164 | république du tchad,td 1165 | tcd,td 1166 | td,td 1167 | atf,tf 1168 | tf,tf 1169 | french southern territories,tf 1170 | french southern and antarctic lands,tf 1171 | tg,tg 1172 | tgo,tg 1173 | togo,tg 1174 | kingdom of thailand,th 1175 | siam,th 1176 | th,th 1177 | tha,th 1178 | thailand,th 1179 | ประเทศสยาม,th 1180 | ประเทศไทย,th 1181 | ราชอาณาจักรไทย,th 1182 | tajikistan,tj 1183 | tj,tj 1184 | tjk,tj 1185 | tk,tk 1186 | tkl,tk 1187 | tokelau,tk 1188 | democratic republic of timor-leste,tl 1189 | east timor,tl 1190 | timor-leste,tl 1191 | tl,tl 1192 | tls,tl 1193 | tkm,tm 1194 | tm,tm 1195 | turkmenistan,tm 1196 | republic of tunisia,tn 1197 | tn,tn 1198 | tun,tn 1199 | tunisia,tn 1200 | to,to 1201 | ton,to 1202 | tonga,to 1203 | anatolia,tr 1204 | ottoman empire,tr 1205 | republic of turkey,tr 1206 | tr,tr 1207 | tur,tr 1208 | turkey,tr 1209 | iere,tt 1210 | republic of trinidad and tobago,tt 1211 | trinbago,tt 1212 | trinidad and tobago,tt 1213 | tt,tt 1214 | tto,tt 1215 | ellice islands,tv 1216 | tuv,tv 1217 | tuvalu,tv 1218 | tv,tv 1219 | cathay,tw 1220 | formosa,tw 1221 | republic of china,tw 1222 | roc,tw 1223 | taiwan,tw 1224 | tw,tw 1225 | twn,tw 1226 | zhonggua taipei,tw 1227 | zhongguo,tw 1228 | tanzania,tz 1229 | tz,tz 1230 | tza,tz 1231 | united republic of tanganyika and zanzibar,tz 1232 | united republic of tanzania,tz 1233 | ua,ua 1234 | ukr,ua 1235 | ukraine,ua 1236 | україна,ua 1237 | ug,ug 1238 | uga,ug 1239 | uganda,ug 1240 | umi,um 1241 | um,um 1242 | united states minor outlying islands,um 1243 | u.s.a,us 1244 | united states,us 1245 | united states of america,us 1246 | us,us 1247 | usa,us 1248 | uruguay,uy 1249 | ury,uy 1250 | uy,uy 1251 | uz,uz 1252 | uzb,uz 1253 | uzbekistan,uz 1254 | va,va 1255 | vat,va 1256 | vatican city,va 1257 | holy see,va 1258 | the holy see,va 1259 | holy see (the),va 1260 | hairouna,vc 1261 | saint vincent and the grenadines,vc 1262 | vc,vc 1263 | vct,vc 1264 | ve,ve 1265 | ven,ve 1266 | venezuela,ve 1267 | vgb,vg 1268 | vg,vg 1269 | british virgin islands,vg 1270 | bvi,vg 1271 | virgin islands,vg 1272 | vir,vi 1273 | vi,vi 1274 | usvi,vi 1275 | united states virgin islands,vi 1276 | american virgin islands,vi 1277 | u.s. virgin islands,vi 1278 | virgin islands of the united states,vi 1279 | an nam,vn 1280 | and south vietnam,vn 1281 | champa,vn 1282 | french indochina,vn 1283 | giao chỉ,vn 1284 | lĩnh nam,vn 1285 | parted in north vietnam,vn 1286 | socialist republic of vietnam,vn 1287 | vietnam,vn 1288 | việt nam,vn 1289 | vn,vn 1290 | vnm,vn 1291 | đại việt,vn 1292 | vanuatri,vu 1293 | vanuatu,vu 1294 | vu,vu 1295 | vut,vu 1296 | wlf,wf 1297 | wf,wf 1298 | wallis and futuna,wf 1299 | territory of the wallis and futuna isla,wf 1300 | samoa,ws 1301 | ws,ws 1302 | wsm,ws 1303 | kosovo,xk 1304 | republic of kosovo,xk 1305 | republika e kosovës,xk 1306 | republika kosovo,xk 1307 | xk,xk 1308 | република косово,xk 1309 | ye,ye 1310 | yem,ye 1311 | yemen,ye 1312 | mayotte,yt 1313 | myt,yt 1314 | yt,yt 1315 | south africa,za 1316 | suid-afrika,za 1317 | union of south africa,za 1318 | za,za 1319 | zaf,za 1320 | zuid-afrika,za 1321 | northern rhodesia,zm 1322 | republic of zambia,zm 1323 | zambia,zm 1324 | zm,zm 1325 | zmb,zm 1326 | republic of zimbabwe,zw 1327 | southern rhodesia,zw 1328 | zimbabwe,zw 1329 | zw,zw 1330 | zwe,zw 1331 | -------------------------------------------------------------------------------- /bar_chart_race/_codes/nfl.csv: -------------------------------------------------------------------------------- 1 | code,value 2 | arizona cardinals,https://www.thesportsdb.com/images/media/team/badge/xvuwtw1420646838.png/preview 3 | atlanta falcons,https://www.thesportsdb.com/images/media/team/badge/rrpvpr1420658174.png/preview 4 | baltimore ravens,https://www.thesportsdb.com/images/media/team/badge/einz3p1546172463.png/preview 5 | buffalo bills,https://www.thesportsdb.com/images/media/team/badge/6pb37b1515849026.png/preview 6 | carolina panthers,https://www.thesportsdb.com/images/media/team/badge/xxyvvy1420940478.png/preview 7 | chicago bears,https://www.thesportsdb.com/images/media/team/badge/uwtwtv1420941123.png/preview 8 | cincinnati bengals,https://www.thesportsdb.com/images/media/team/badge/qqtwwv1420941670.png/preview 9 | cleveland browns,https://www.thesportsdb.com/images/media/team/badge/squvxy1420942389.png/preview 10 | dallas cowboys,https://www.thesportsdb.com/images/media/team/badge/wrxssu1450018209.png/preview 11 | denver broncos,https://www.thesportsdb.com/images/media/team/badge/upsspx1421635647.png/preview 12 | detroit lions,https://www.thesportsdb.com/images/media/team/badge/lgsgkr1546168257.png/preview 13 | green bay packers,https://www.thesportsdb.com/images/media/team/badge/rqpwtr1421434717.png/preview 14 | houston texans,https://www.thesportsdb.com/images/media/team/badge/wqyryy1421436627.png/preview 15 | indianapolis colts,https://www.thesportsdb.com/images/media/team/badge/wqqvpx1421434058.png/preview 16 | jacksonville jaguars,https://www.thesportsdb.com/images/media/team/badge/0mrsd41546427902.png/preview 17 | kansas city chiefs,https://www.thesportsdb.com/images/media/team/badge/936t161515847222.png/preview 18 | las vegas raiders,https://www.thesportsdb.com/images/media/team/badge/xqusqy1421724291.png/preview 19 | los angeles chargers,https://www.thesportsdb.com/images/media/team/badge/wbhu3a1548320628.png/preview 20 | los angeles rams,https://www.thesportsdb.com/images/media/team/badge/45ps1l1515847886.png/preview 21 | miami dolphins,https://www.thesportsdb.com/images/media/team/badge/trtusv1421435081.png/preview 22 | minnesota vikings,https://www.thesportsdb.com/images/media/team/badge/qstqqr1421609163.png/preview 23 | new england patriots,https://www.thesportsdb.com/images/media/team/badge/xtwxyt1421431860.png/preview 24 | new orleans saints,https://www.thesportsdb.com/images/media/team/badge/nd46c71537821337.png/preview 25 | new york giants,https://www.thesportsdb.com/images/media/team/badge/vxppup1423669459.png/preview 26 | new york jets,https://www.thesportsdb.com/images/media/team/badge/rurvuu1421435228.png/preview 27 | philadelphia eagles,https://www.thesportsdb.com/images/media/team/badge/pnpybf1515852421.png/preview 28 | pittsburgh steelers,https://www.thesportsdb.com/images/media/team/badge/2975411515853129.png/preview 29 | san francisco 49ers,https://www.thesportsdb.com/images/media/team/badge/bqbtg61539537328.png/preview 30 | seattle seahawks,https://www.thesportsdb.com/images/media/team/badge/wwuqyr1421434817.png/preview 31 | tampa bay buccaneers,https://www.thesportsdb.com/images/media/team/badge/2dfpdl1537820969.png/preview 32 | tennessee titans,https://www.thesportsdb.com/images/media/team/badge/m48yia1515847376.png/preview 33 | washington redskins,https://www.thesportsdb.com/images/media/team/badge/qsxwrv1425081054.png/preview 34 | -------------------------------------------------------------------------------- /bar_chart_race/_common_chart.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from matplotlib import ticker 3 | 4 | 5 | class CommonChart: 6 | 7 | def get_extension(self): 8 | if self.filename: 9 | return self.filename.split('.')[-1] 10 | 11 | def get_title(self, title): 12 | if isinstance(title, str): 13 | return {'label': title} 14 | elif isinstance(title, dict): 15 | if 'label' not in title: 16 | raise ValueError('You must use the key "label" in the `title` dictionary ' 17 | 'to supply the name of the title') 18 | elif title is not None: 19 | raise TypeError('`title` must be either a string or dictionary') 20 | else: 21 | return {'label': None} 22 | return title 23 | 24 | def get_tick_template(self, tick_template): 25 | if isinstance(tick_template, str): 26 | return ticker.StrMethodFormatter(tick_template) 27 | elif callable(tick_template): 28 | return ticker.FuncFormatter(tick_template) 29 | 30 | def set_shared_fontdict(self, shared_fontdict): 31 | orig_rcParams = plt.rcParams.copy() 32 | if shared_fontdict is None: 33 | return orig_rcParams 34 | for k, v in shared_fontdict.items(): 35 | if k not in ['fontsize', 'size']: 36 | if k in ['cursive', 'family', 'fantasy', 'monospace', 'sans-serif', 'serif']: 37 | if isinstance(v, str): 38 | v = [v] 39 | if k == 'color': 40 | plt.rcParams['text.color'] = v 41 | plt.rcParams['xtick.color'] = v 42 | plt.rcParams['ytick.color'] = v 43 | continue 44 | try: 45 | plt.rcParams[f'font.{k}'] = v 46 | except KeyError: 47 | raise KeyError(f"{k} is not a valid key in `sharedfont_dict`" 48 | "It must be one of " 49 | "'cursive', 'family', 'fantasy', 'monospace', 'sans-serif'," 50 | "'serif', 'stretch','style', 'variant', 'weight'") 51 | return orig_rcParams 52 | 53 | def get_writer(self, writer): 54 | if writer is None: 55 | if self.extension == 'gif': 56 | writer = 'imagemagick' 57 | elif self.extension == 'html': 58 | writer = 'html' 59 | else: 60 | writer = plt.rcParams['animation.writer'] 61 | return writer 62 | 63 | def get_fig_kwargs(self, fig_kwargs): 64 | default_fig_kwargs = {'figsize': (6, 3.5), 'dpi': 144} 65 | if fig_kwargs is None: 66 | return default_fig_kwargs 67 | if isinstance(fig_kwargs, dict): 68 | fig_kwargs = {**default_fig_kwargs, **fig_kwargs} 69 | else: 70 | raise TypeError('fig_kwargs must be a dict or None') 71 | return fig_kwargs 72 | 73 | def get_fig(self, fig): 74 | if fig is not None and not isinstance(fig, plt.Figure): 75 | raise TypeError('`fig` must be a matplotlib Figure instance') 76 | if fig is not None: 77 | if not fig.axes: 78 | raise ValueError('The figure passed to `fig` must have an axes') 79 | ax = fig.axes[0] 80 | else: 81 | fig = self.create_figure() 82 | return fig 83 | -------------------------------------------------------------------------------- /bar_chart_race/_func_animation.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from io import BytesIO, TextIOWrapper 3 | from pathlib import Path 4 | from tempfile import TemporaryDirectory 5 | 6 | from matplotlib import rcParams 7 | from matplotlib import animation 8 | 9 | class FuncAnimation(animation.FuncAnimation): 10 | 11 | def to_html5_video(self, embed_limit=None, savefig_kwargs=None): 12 | """ 13 | Convert the animation to an HTML5 ```` tag. 14 | 15 | This saves the animation as an h264 video, encoded in base64 16 | directly into the HTML5 video tag. This respects the rc parameters 17 | for the writer as well as the bitrate. This also makes use of the 18 | ``interval`` to control the speed, and uses the ``repeat`` 19 | parameter to decide whether to loop. 20 | 21 | Parameters 22 | ---------- 23 | embed_limit : float, optional 24 | Limit, in MB, of the returned animation. No animation is created 25 | if the limit is exceeded. 26 | Defaults to :rc:`animation.embed_limit` = 20.0. 27 | 28 | Returns 29 | ------- 30 | video_tag : str 31 | An HTML5 video tag with the animation embedded as base64 encoded 32 | h264 video. 33 | If the *embed_limit* is exceeded, this returns the string 34 | "Video too large to embed." 35 | """ 36 | VIDEO_TAG = r''' 37 | 38 | Your browser does not support the video tag. 39 | ''' 40 | # Cache the rendering of the video as HTML 41 | if not hasattr(self, '_base64_video'): 42 | # Save embed limit, which is given in MB 43 | if embed_limit is None: 44 | embed_limit = rcParams['animation.embed_limit'] 45 | 46 | # Convert from MB to bytes 47 | embed_limit *= 1024 * 1024 48 | 49 | # Can't open a NamedTemporaryFile twice on Windows, so use a 50 | # TemporaryDirectory instead. 51 | with TemporaryDirectory() as tmpdir: 52 | path = Path(tmpdir, "temp.m4v") 53 | # We create a writer manually so that we can get the 54 | # appropriate size for the tag 55 | Writer = animation.writers[rcParams['animation.writer']] 56 | writer = Writer(codec='h264', 57 | bitrate=rcParams['animation.bitrate'], 58 | fps=1000. / self._interval) 59 | self.save(str(path), writer=writer, savefig_kwargs=savefig_kwargs) 60 | # Now open and base64 encode. 61 | vid64 = base64.encodebytes(path.read_bytes()) 62 | 63 | vid_len = len(vid64) 64 | if vid_len >= embed_limit: 65 | _log.warning( 66 | "Animation movie is %s bytes, exceeding the limit of %s. " 67 | "If you're sure you want a large animation embedded, set " 68 | "the animation.embed_limit rc parameter to a larger value " 69 | "(in MB).", vid_len, embed_limit) 70 | else: 71 | self._base64_video = vid64.decode('ascii') 72 | self._video_size = 'width="{}" height="{}"'.format( 73 | *writer.frame_size) 74 | 75 | # If we exceeded the size, this attribute won't exist 76 | if hasattr(self, '_base64_video'): 77 | # Default HTML5 options are to autoplay and display video controls 78 | options = ['controls', 'autoplay'] 79 | 80 | # If we're set to repeat, make it loop 81 | if hasattr(self, 'repeat') and self.repeat: 82 | options.append('loop') 83 | 84 | return VIDEO_TAG.format(video=self._base64_video, 85 | size=self._video_size, 86 | options=' '.join(options)) 87 | else: 88 | return 'Video too large to embed.' -------------------------------------------------------------------------------- /bar_chart_race/_line_chart_race.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from pathlib import Path 3 | 4 | import pandas as pd 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from ._func_animation import FuncAnimation 8 | from matplotlib.collections import LineCollection 9 | from matplotlib import ticker, colors as mcolors, dates as mdates 10 | from matplotlib import image as mimage 11 | from matplotlib import patches as mpatches 12 | 13 | from ._common_chart import CommonChart 14 | from ._utils import prepare_wide_data 15 | 16 | 17 | OTHERS_COLOR = .7, .7, .7, .6 18 | AGG_COLOR = 0, 0, 0, 1 19 | 20 | 21 | class _LineChartRace(CommonChart): 22 | 23 | def __init__(self, df, filename, n_lines, steps_per_period, period_length, 24 | end_period_pause, period_summary_func, line_width_data, agg_line_func, 25 | agg_line_kwargs, others_line_func, others_line_kwargs, fade, min_fade, 26 | images, colors, title, line_label_font, tick_label_font, tick_template, 27 | shared_fontdict, scale, fig, writer, line_kwargs, fig_kwargs): 28 | self.filename = filename 29 | self.extension = self.get_extension() 30 | self.n_lines = n_lines or df.shape[1] 31 | self.steps_per_period = steps_per_period 32 | self.period_length = period_length 33 | self.end_period_pause = end_period_pause 34 | self.period_summary_func = period_summary_func 35 | self.agg_line_func = agg_line_func 36 | self.agg_line_kwargs, self.agg_line_label = self.get_line_kwargs(agg_line_kwargs, 'agg') 37 | self.others_line_func = others_line_func 38 | self.others_line_kwargs, self.others_line_label = self.get_line_kwargs(others_line_kwargs, 'others') 39 | self.line_width_data = self.get_line_width_data(line_width_data) 40 | self.fade = fade 41 | self.min_fade = min_fade 42 | self.title = self.get_title(title) 43 | self.line_label_font = self.get_font(line_label_font) 44 | self.tick_label_font = self.get_font(tick_label_font, True) 45 | self.tick_template = self.get_tick_template(tick_template) 46 | self.orig_rcParams = self.set_shared_fontdict(shared_fontdict) 47 | self.scale = scale 48 | self.writer = self.get_writer(writer) 49 | self.fps = 1000 / self.period_length * steps_per_period 50 | self.validate_params() 51 | 52 | self.line_kwargs = self.get_line_kwargs(line_kwargs, 'line') 53 | self.html = self.filename is None 54 | self.all_values, self.df_values, self.df_ranks, self.df_others, self.others_agg_line = self.prepare_data(df) 55 | self.agg_line = self.prepare_agg_line() 56 | self.is_x_date = self.df_values.index.dtype.kind == 'M' 57 | self.colors = self.get_colors(colors) 58 | self.str_index = self.df_values.index.astype('str') 59 | self.fig_kwargs = self.get_fig_kwargs(fig_kwargs) 60 | self.subplots_adjust = self.get_subplots_adjust() 61 | self.fig = self.get_fig(fig) 62 | self.collections = {} 63 | self.other_collections = {} 64 | self.texts = {} 65 | self.images = self.get_images(images) 66 | self.image_radius = self.fig.get_figwidth() * self.fig.dpi * .02 67 | 68 | def get_line_kwargs(self, kwargs, kind): 69 | defaults = {'lw': 1.5, 'ls': '-'} 70 | text = None 71 | if kind == 'others': 72 | defaults['color'] = OTHERS_COLOR 73 | elif kind == 'agg': 74 | defaults['color'] = AGG_COLOR 75 | elif kind == 'line': 76 | defaults['alpha'] = 1 77 | 78 | if kwargs is None: 79 | kwargs = defaults 80 | elif isinstance(kwargs, dict): 81 | kwargs = {**defaults, **kwargs} 82 | if 's' in kwargs: 83 | text = kwargs.pop('s') 84 | else: 85 | raise TypeError(f'{kind}_line_kwargs must be a dictionary with line properties') 86 | 87 | if 'linewidth' in kwargs: 88 | kwargs['lw'] = kwargs.pop('linewidth') 89 | if 'linestyle' in kwargs: 90 | kwargs['ls'] = kwargs.pop('linestyle') 91 | if kind == 'line': 92 | return kwargs 93 | if 'c' in kwargs: 94 | kwargs['color'] = kwargs.pop('c') 95 | 96 | kwargs['color'] = mcolors.to_rgba(kwargs['color']) 97 | return kwargs, text 98 | 99 | def get_line_width_data(self, line_width_data): 100 | if line_width_data is None: 101 | return 102 | df = line_width_data.copy() 103 | min_val = df.min().min() 104 | df = df - min_val 105 | max_val = df.max().max() 106 | df = df / max_val 107 | df = df * 6 + 1 108 | return df 109 | 110 | def validate_params(self): 111 | if isinstance(self.filename, str): 112 | if '.' not in self.filename: 113 | raise ValueError('`filename` must have an extension') 114 | elif self.filename is not None: 115 | raise TypeError('`filename` must be None or a string') 116 | 117 | def get_font(self, font, ticks=False): 118 | default_font_dict = {'size': 7, 'ha': 'left', 'va': 'center'} 119 | if ticks: 120 | default_font_dict['ha'] = 'center' 121 | 122 | if font is None: 123 | font = default_font_dict 124 | elif isinstance(font, (int, float, str)): 125 | font = {**default_font_dict, 'size': font} 126 | elif not isinstance(font, dict): 127 | raise TypeError('`font` must be a number or dictionary of font properties') 128 | else: 129 | font = {**default_font_dict, **font} 130 | return font 131 | 132 | def prepare_data(self, df): 133 | sort = 'desc' 134 | interpolate_period = True 135 | compute_ranks = sort = True 136 | orientation = 'h' 137 | values, ranks = prepare_wide_data(df, orientation, sort, self.n_lines, interpolate_period, 138 | self.steps_per_period, compute_ranks) 139 | 140 | idx = values.iloc[-1].sort_values(ascending=False).index 141 | top_cols, other_cols = idx[:self.n_lines], idx[self.n_lines:] 142 | all_values = values.copy() 143 | values, ranks, others = values[top_cols], ranks[top_cols], values[other_cols] 144 | 145 | if self.others_line_func in (None, True) or len(other_cols) == 0: 146 | others_agg_line = None 147 | else: 148 | others_agg_line = self.prepare_others_agg_line(others) 149 | return all_values, values, ranks, others, others_agg_line 150 | 151 | def prepare_others_agg_line(self, others): 152 | if isinstance(self.others_line_func, str): 153 | s_others = others.agg(self.others_line_func, axis=1) 154 | label = self.others_line_func 155 | elif callable(self.others_line_func): 156 | s_others = others.agg(self.others_line_func, axis=1) 157 | label = self.others_line_func.__name__ 158 | else: 159 | raise TypeError('`others_line_func` must be either a string or function') 160 | 161 | if self.others_line_label is None: 162 | self.others_line_label = label 163 | return s_others 164 | 165 | def prepare_agg_line(self): 166 | if self.agg_line_func is None: 167 | return 168 | if isinstance(self.agg_line_func, str): 169 | s_agg = self.all_values.agg(self.agg_line_func, axis=1) 170 | label = self.agg_line_func 171 | elif callable(self.agg_line_func): 172 | s_agg = self.all_values.agg(self.agg_line_func, axis=1) 173 | label = self.agg_line_func.__name__ 174 | else: 175 | raise TypeError('`agg_line_func` must be either a string or function') 176 | 177 | if self.agg_line_label is None: 178 | self.agg_line_label = label 179 | return s_agg 180 | 181 | def get_colors(self, colors): 182 | if colors is None: 183 | colors = 'dark12' 184 | if self.df_values.shape[1] > 10: 185 | colors = 'dark24' 186 | 187 | if isinstance(colors, str): 188 | from ._colormaps import colormaps 189 | try: 190 | colors = colormaps[colors.lower()] 191 | except KeyError: 192 | raise KeyError(f'Colormap {colors} does not exist. Here are the ' 193 | f'possible colormaps: {colormaps.keys()}') 194 | elif isinstance(colors, mcolors.Colormap): 195 | colors = colors(range(colors.N)).tolist() 196 | elif isinstance(colors, list): 197 | colors = colors 198 | elif isinstance(colors, tuple): 199 | colors = list(colors) 200 | elif hasattr(colors, 'tolist'): 201 | colors = colors.tolist() 202 | else: 203 | raise TypeError('`colors` must be a string name of a colormap, a matplotlib colormap ' 204 | 'instance, list, or tuple of colors') 205 | 206 | # colors is a list 207 | n = len(colors) 208 | if self.df_values.shape[1] > n: 209 | colors = colors * (self.df_values.shape[1] // n + 1) 210 | 211 | colors = mcolors.to_rgba_array(colors) 212 | colors = colors[:self.df_values.shape[1]] 213 | return dict(zip(self.df_values.columns, colors)) 214 | 215 | def prepare_axes(self, ax): 216 | ax.grid(True, color='white') 217 | ax.tick_params(labelsize=self.tick_label_font['size'], length=0, pad=2) 218 | ax.minorticks_off() 219 | ax.set_axisbelow(True) 220 | ax.set_facecolor('.9') 221 | ax.set_title(**self.title) 222 | 223 | min_val = self.df_values.min().min() 224 | max_val = self.df_values.max().max() 225 | 226 | if self.others_line_func is True: 227 | min_val = min(min_val, self.df_others.min().min()) 228 | max_val = max(max_val, self.df_others.max().max()) 229 | elif self.others_agg_line is not None: 230 | min_val = min(min_val, self.others_agg_line.min()) 231 | max_val = max(max_val, self.others_agg_line.max()) 232 | 233 | if self.agg_line is not None: 234 | min_val = min(min_val, self.agg_line.min()) 235 | max_val = max(max_val, self.agg_line.max()) 236 | min_val = 1 if self.scale == 'log' else min_val 237 | 238 | ax.set_yscale(self.scale) 239 | 240 | for spine in ax.spines.values(): 241 | spine.set_visible(False) 242 | 243 | ax.set_xlim(self.df_values.index[0], self.df_values.index[-1]) 244 | ax.set_ylim(min_val, max_val) 245 | xmin, xmax = ax.get_xlim() 246 | delta = (xmax - xmin) * .05 247 | ax.set_xlim(xmin - delta, xmax + delta) 248 | 249 | ymin, ymax = ax.get_ylim() 250 | delta = (ymax - ymin) * .05 251 | if self.scale == 'log': 252 | delta = 0 253 | ymax = 2 * ymax 254 | ax.set_ylim(ymin - delta, ymax + delta) 255 | 256 | if self.is_x_date: 257 | ax.xaxis_date() 258 | ax.xaxis.set_major_formatter(mdates.DateFormatter('%-m/%-d')) 259 | 260 | if self.tick_template: 261 | ax.yaxis.set_major_formatter(self.tick_template) 262 | 263 | def get_subplots_adjust(self): 264 | import io 265 | fig = plt.Figure(**self.fig_kwargs) 266 | ax = fig.add_subplot() 267 | ax.plot(self.df_values) 268 | 269 | self.prepare_axes(ax) 270 | fig.canvas.print_figure(io.BytesIO(), format='png') 271 | xmin = min(label.get_window_extent().x0 for label in ax.get_yticklabels()) 272 | xmin /= (fig.dpi * fig.get_figwidth()) 273 | left = ax.get_position().x0 - xmin + .01 274 | 275 | ymin = min(label.get_window_extent().y0 for label in ax.get_xticklabels()) 276 | ymin /= (fig.dpi * fig.get_figheight()) 277 | bottom = ax.get_position().y0 - ymin + .01 278 | return left, bottom 279 | 280 | def get_fig(self, fig): 281 | if fig is not None and not isinstance(fig, plt.Figure): 282 | raise TypeError('`fig` must be a matplotlib Figure instance') 283 | if fig is not None: 284 | if not fig.axes: 285 | raise ValueError('The figure passed to `fig` must have an axes') 286 | ax = fig.axes[0] 287 | self.prepare_axes(ax) 288 | else: 289 | fig = self.create_figure() 290 | return fig 291 | 292 | def create_figure(self): 293 | fig = plt.Figure(**self.fig_kwargs) 294 | ax = fig.add_subplot() 295 | left, bottom = self.subplots_adjust 296 | fig.subplots_adjust(left=left, bottom=bottom) 297 | self.prepare_axes(ax) 298 | return fig 299 | 300 | def get_images(self, images): 301 | if images is None: 302 | return 303 | if isinstance(images, str): 304 | from ._utils import read_images 305 | return read_images(images.lower(), self.df_values.columns) 306 | if len(images) != len(self.df_values.columns): 307 | raise ValueError('The number of images does not match the number of columns') 308 | if isinstance(images, list): 309 | images = dict(zip(self.df_values.columns, images)) 310 | return {col: mimage.imread(image) for col, image in images.items()} 311 | 312 | def get_visible(self, i): 313 | n = 1_000_000 # make all visible until better logic here 314 | return (self.df_ranks.iloc[i] <= self.n_lines + n + .5).to_dict() 315 | 316 | def add_period_summary(self, ax, s): 317 | if self.period_summary_func: 318 | text_dict = self.period_summary_func(s) 319 | if 'x' not in text_dict or 'y' not in text_dict or 's' not in text_dict: 320 | name = self.period_summary_func.__name__ 321 | raise ValueError(f'The dictionary returned from `{name}` must contain ' 322 | '"x", "y", and "s"') 323 | return text_dict 324 | 325 | def anim_func(self, i): 326 | if i is None: 327 | return 328 | ax = self.fig.axes[0] 329 | s = self.df_values.iloc[i] 330 | s_all = self.all_values.iloc[i] 331 | x, y = s.name, s.to_dict() 332 | if self.is_x_date: 333 | x = mdates.date2num(x) 334 | 335 | if self.images: 336 | xmin, xmax = ax.get_xlim() 337 | x_extra = (xmax - xmin) * .025 338 | else: 339 | x_extra = 0 340 | 341 | visible = self.get_visible(i) 342 | 343 | if self.agg_line is not None: 344 | y['___agg_line___'] = self.agg_line.iloc[i] 345 | visible['___agg_line___'] = True 346 | 347 | if self.others_agg_line is not None: 348 | y['___others_line___'] = self.others_agg_line.iloc[i] 349 | visible['___others_line___'] = True 350 | 351 | for col, collection in self.collections.items(): 352 | text = self.texts[col] 353 | val = y[col] 354 | color = self.colors[col] 355 | vis = visible[col] 356 | 357 | seg = collection.get_segments() 358 | last = seg[-1][-1] 359 | new_seg = np.row_stack((last, [x, val])) 360 | seg.append(new_seg) 361 | collection.set_segments(seg) 362 | color_arr = collection.get_colors() 363 | 364 | color_arr = np.append(color_arr, [color], axis=0) 365 | color_arr[:, -1] = np.clip(color_arr[:, -1] * self.fade, self.min_fade, None) 366 | collection.set_color(color_arr) 367 | 368 | is_other_agg = col in ('___others_line___', '___agg_line___') 369 | if self.line_width_data is not None and not is_other_agg: 370 | lw = self.line_width_data.iloc[i // self.steps_per_period][col] 371 | lw_arr = collection.get_linewidths() 372 | lw_arr = np.append(lw_arr, [lw], axis=0) 373 | collection.set_linewidths(lw_arr) 374 | 375 | text.set_position((x + x_extra, val)) 376 | text.set_visible(vis) 377 | collection.set_visible(vis) 378 | 379 | if self.others_line_func is True: 380 | y_other = self.df_others.iloc[i].to_dict() 381 | for col, collection in self.other_collections.items(): 382 | val = y_other[col] 383 | seg = collection.get_segments() 384 | last = seg[-1][-1] 385 | new_seg = np.row_stack((last, [x, val])) 386 | seg.append(new_seg) 387 | collection.set_segments(seg) 388 | 389 | if self.period_summary_func: 390 | text_dict = self.add_period_summary(ax, s_all) 391 | text = self.texts['__period_summary_func__'] 392 | x_period, y_period, text_val = text_dict.pop('x'), text_dict.pop('y'), text_dict.pop('s') 393 | text.set_position((x_period, y_period)) 394 | text.set_text(text_val) 395 | 396 | if self.images: 397 | for col in self.df_values.columns: 398 | xpixel, ypixel = ax.transData.transform((x, y[col])) 399 | center = xpixel, ypixel 400 | left, right = xpixel - self.image_radius, xpixel + self.image_radius 401 | bottom, top = ypixel - self.image_radius, ypixel + self.image_radius 402 | img, circle = self.images[col] 403 | img.set_extent([left, right, bottom, top]) 404 | circle.set_center(center) 405 | img.set_clip_path(circle) 406 | vis = visible[col] 407 | img.set_visible(vis) 408 | 409 | def init_func(self): 410 | ax = self.fig.axes[0] 411 | s = self.df_values.iloc[0] # current Series 412 | s_all = self.all_values.iloc[0] 413 | if len(self.df_others) > 0: 414 | s_others = self.df_others.iloc[0] 415 | x, y = s.name, s.to_dict() 416 | if self.is_x_date: 417 | x = mdates.date2num(x) 418 | 419 | if self.images: 420 | xmin, xmax = ax.get_xlim() 421 | x_extra = (xmax - xmin) * .025 422 | else: 423 | x_extra = 0 424 | 425 | visible = self.get_visible(0) 426 | 427 | for col in self.df_values.columns: 428 | val = y[col] 429 | vis = visible[col] 430 | color = self.colors[col] 431 | text = ax.text(x + x_extra, val, col, visible=vis, **self.line_label_font) 432 | lw = self.line_kwargs['lw'] 433 | ls = self.line_kwargs['ls'] 434 | alpha = self.line_kwargs.get('alpha', 1) 435 | color[-1] = alpha 436 | if self.line_width_data is not None: 437 | lw = self.line_width_data.iloc[0][col] 438 | lc = LineCollection([[(x, val)]], colors=[color], visible=vis, linewidths=[lw], linestyles=[ls]) 439 | collection = ax.add_collection(lc) 440 | self.texts[col] = text 441 | self.collections[col] = collection 442 | 443 | if self.others_line_func is True: 444 | y_other = self.df_others.iloc[0].to_dict() 445 | for col, val in y_other.items(): 446 | collection = ax.add_collection(LineCollection([[(x, val)]], colors=[OTHERS_COLOR])) 447 | self.other_collections[col] = collection 448 | 449 | if self.agg_line is not None: 450 | color = self.agg_line_kwargs['color'] 451 | lw = self.agg_line_kwargs.get('lw') 452 | ls = self.agg_line_kwargs.get('ls') 453 | alpha = self.agg_line_kwargs.get('alpha', AGG_COLOR[-1]) 454 | color = tuple(color[:3]) + (alpha,) 455 | label = self.agg_line_label 456 | val = self.agg_line.iloc[0] 457 | lc = LineCollection([[(x, val)]], colors=[color], linewidths=[lw], linestyles=[ls]) 458 | collection = ax.add_collection(lc) 459 | text = ax.text(x + x_extra, val, label, **self.line_label_font) 460 | 461 | label = '___agg_line___' 462 | self.collections[label] = collection 463 | self.texts[label] = text 464 | self.colors[label] = color 465 | 466 | if self.others_agg_line is not None: 467 | color = self.others_line_kwargs['color'] 468 | lw = self.others_line_kwargs.get('lw') 469 | ls = self.others_line_kwargs.get('ls') 470 | alpha = self.others_line_kwargs.get('alpha', OTHERS_COLOR[-1]) 471 | color = tuple(color[:3]) + (alpha,) 472 | label = self.others_line_label 473 | val = self.others_agg_line.iloc[0] 474 | lc = LineCollection([[(x, val)]], colors=[color], linewidths=[lw], linestyles=[ls]) 475 | collection = ax.add_collection(lc) 476 | text = ax.text(x + x_extra, val, label, ha='left', va='center', size='smaller') 477 | 478 | label = '___others_line___' 479 | self.collections[label] = collection 480 | self.texts[label] = text 481 | self.colors[label] = color 482 | 483 | if self.period_summary_func: 484 | text_dict = self.add_period_summary(ax, s_all) 485 | text = ax.text(transform=ax.transAxes, **text_dict) 486 | self.texts['__period_summary_func__'] = text 487 | 488 | if self.images: 489 | for col in self.df_values.columns: 490 | xpixel, ypixel = ax.transData.transform((x, y[col])) 491 | center = xpixel, ypixel 492 | circle = mpatches.Circle(center, transform=None, radius=self.image_radius, fill=None, lw=0) 493 | ax.add_patch(circle) 494 | left, right = xpixel - self.image_radius, xpixel + self.image_radius 495 | bottom, top = ypixel - self.image_radius, ypixel + self.image_radius 496 | img_array = self.images[col] 497 | img = ax.imshow(img_array, extent=[left, right, bottom, top], aspect='auto', transform=None, zorder=4) 498 | img.set_clip_path(circle) 499 | vis = visible[col] 500 | img.set_visible(vis) 501 | self.images[col] = img, circle 502 | 503 | def make_animation(self): 504 | interval = self.period_length / self.steps_per_period 505 | pause = int(self.end_period_pause // interval) 506 | 507 | def frame_generator(n): 508 | frames = [] 509 | for i in range(1, n): 510 | frames.append(i) 511 | if pause and i % self.steps_per_period == 0 and i != 0 and i != n - 1: 512 | for _ in range(pause): 513 | frames.append(None) 514 | return frames 515 | 516 | frames = frame_generator(len(self.df_values)) 517 | anim = FuncAnimation(self.fig, self.anim_func, frames, self.init_func, interval=interval) 518 | 519 | try: 520 | fc = self.fig.get_facecolor() 521 | if fc == (1, 1, 1, 0): 522 | fc = 'white' 523 | savefig_kwargs = {'facecolor': fc} 524 | if self.html: 525 | ret_val = anim.to_html5_video(savefig_kwargs=savefig_kwargs) 526 | try: 527 | from IPython.display import HTML 528 | ret_val = HTML(ret_val) 529 | except ImportError: 530 | pass 531 | else: 532 | 533 | ret_val = anim.save(self.filename, fps=self.fps, writer=self.writer, 534 | savefig_kwargs=savefig_kwargs) 535 | except Exception as e: 536 | message = str(e) 537 | raise Exception(message) 538 | finally: 539 | plt.rcParams = self.orig_rcParams 540 | 541 | return ret_val 542 | 543 | 544 | def line_chart_race(df, filename=None, n_lines=None, steps_per_period=10, 545 | period_length=500, end_period_pause=0, period_summary_func=None, 546 | line_width_data=None, agg_line_func=None, agg_line_kwargs=None, 547 | others_line_func=None, others_line_kwargs=None, fade=1, min_fade=.3, 548 | images=None, colors=None, title=None, line_label_font=None, 549 | tick_label_font=None, tick_template='{x:,.0f}', shared_fontdict=None, 550 | scale='linear', fig=None, writer=None, line_kwargs=None, 551 | fig_kwargs=None): 552 | ''' 553 | Create an animated line chart race using matplotlib. Data must be in 554 | 'wide' format where each row represents a single time period and each 555 | column represents a distinct category. Optionally, the index can label 556 | the time period. 557 | 558 | If no `filename` is given, an HTML string is returned, otherwise the 559 | animation is saved to disk. 560 | 561 | You must have ffmpeg installed on your machine to save videos to disk 562 | and ImageMagick to save animated gifs. Read more here: 563 | https://www.dexplo.org/bar_chart_race/installation/ 564 | 565 | Parameters 566 | ---------- 567 | df : pandas DataFrame 568 | Must be a 'wide' DataFrame where each row represents a single period 569 | of time. Each column contains the values of the lines for that 570 | category. Optionally, use the index to label each time period. 571 | The index can be of any type. 572 | 573 | filename : `None` or str, default None 574 | If `None` return animation as an HTML5 string. If a string, save 575 | animation to that filename location. Use .mp4, .gif, .html, .mpeg, 576 | .mov or any other extensions supported by ffmpeg or ImageMagick. 577 | 578 | n_lines : int, default None 579 | The maximum number of lines to display on the graph. 580 | When there are more columns than n_lines, the columns 581 | chosen will be those with the highest values in the last 582 | period. 583 | 584 | See the others_line_func parameter for more options on plotting 585 | the other columns outside of the top n_lines. Use all lines by default. 586 | 587 | steps_per_period : int, default 10 588 | The number of steps to go from one time period to the next. 589 | Increasing this number creates smoother animations. 590 | 591 | period_length : int, default 500 592 | Number of milliseconds to animate each period (row). 593 | Default is 500ms (half of a second). 594 | 595 | end_period_pause : int, default 0 596 | Number of milliseconds to pause the animation at the end of 597 | each period. This number must be greater than or equal to 598 | period_length / steps_per_period or there will be no pause. 599 | This is due to all frames having the same time interval. 600 | 601 | By default, each frame is 500 / 10 or 50 milliseconds, 602 | therefore end_period_pause must be at least 50 for there 603 | to be a pause. The pause will be in increments of this 604 | calculated interval and not exact. For example, setting the 605 | end_period_pause to 725 will produce a pause of 700 606 | milliseconds when using the defaults. 607 | 608 | period_summary_func : function, default None 609 | Custom text added to the axes each period. 610 | Create a user-defined function that accepts one pandas Series of the 611 | current time period's values. It must return a dictionary containing 612 | the keys "x", "y", and "s" which will be passed to the matplotlib 613 | `text` method. 614 | Example: 615 | def func(values): 616 | total = values.sum() 617 | s = f'Worldwide deaths: {total}' 618 | return {'x': .05, 'y': .85, 's': s, 'size': 10} 619 | 620 | line_width_data : DataFrame, default None 621 | Control the width of the line at each time period. 622 | Provide a separate DataFrame with the same index, columns, and 623 | dimensions as the original. Line width will be scaled so that 624 | it is between 1 and 6 points. 625 | 626 | agg_line_func : function or str, default None 627 | Create an additional line to summarize all of the data. Pass it either 628 | a string that the DataFrame `agg` method understands or a user-defined 629 | function. Use agg_line_kwargs to style the line and provide it a label. 630 | 631 | If providing function, it will be passed all values of the current 632 | period as a Series. Return a single value that summarizes the current 633 | period. 634 | 635 | DataFrame agg strings - 'mean', 'median', 'max', 'min', etc.. 636 | 637 | agg_line_kwargs : dict, default None 638 | A dictionary of matplotlib line properties used with agg_line_func. 639 | Use the key `s` to control the label of the line. Keys `x` and `y` 640 | will be ignored as the position is already determined. 641 | Example: 642 | { 643 | 's': 'Median', 644 | 'color': '.2', 645 | 'lw': 3, 646 | 'ls': '--', 647 | } 648 | 649 | others_line_func : bool, str, or function, default None 650 | This parameter may be used when there are more columns than n_lines. 651 | By default (None), these other lines will not be plotted. Use True to 652 | plot the lines in a soft gray color without labels or images. 653 | 654 | Aggregate all of the other column values by passing in a string that 655 | the DataFrame agg method understands or a user-defined function, which 656 | will be passed a pandas Series of just these other values for this 657 | time period. 658 | Example: 659 | def my_others_line_func(s): 660 | return s.median() 661 | 662 | others_line_kwargs : dict, default None 663 | A dictionary of matplotlib line properties used with others_line_func. 664 | Use the key `s` to control the label of the line. Keys `x` and `y` 665 | will be ignored as the position is already determined. 666 | Example: 667 | { 668 | 's': 'Rest of World', 669 | 'color': '.2', 670 | 'lw': 3, 671 | 'ls': '--', 672 | } 673 | 674 | fade : float, default 1 675 | Use to slowly fade historical values of the line, i.e. decrease the 676 | opacity (alpha). This number multiplies the current alpha of the line 677 | each time period. 678 | 679 | Choose a number between 0 and 1. When 1, no fading occurs. 680 | This number will likely need to be close to 1, as alpha decreases fast. 681 | 682 | min_fade : float, default .3 683 | Minimum value of alpha for each line when setting fade < 1. Choose a 684 | number between 0 and 1. Use 0 to have the line eventually become 685 | completely transparent. 686 | 687 | images : str, list, or dict, default None 688 | Images to use for the end of the line. Use a string to use one of the 689 | built-in image databases by name. Currently, there are two image bases 690 | 'country', and 'nfl'. If your columns are countries are NFL teams, the 691 | images will automatically be found. 692 | 693 | Otherwise, provide a list of filenames or URLs where the images are 694 | located in the same order as the columns of the DataFrame or a 695 | dictionary mapping column names to filenames/URLs. 696 | 697 | colors : str, matplotlib colormap instance, or list of colors, default 'dark12' 698 | Colors to be used for the lines. All matplotlib and plotly 699 | colormaps are available by string name. Colors will repeat 700 | if there are more lines than colors. 701 | 702 | 'dark12' is a discrete colormap. If there are more than 12 columns, 703 | then the default colormap will be 'dark24' 704 | 705 | Append "_r" to the colormap name to use the reverse of the colormap. 706 | i.e. "dark12_r" 707 | 708 | title : str or dict, default None 709 | Title of plot as a string. Use a dictionary to supply several title 710 | parameters. You must use the key 'label' for the text. 711 | Example: 712 | { 713 | 'label': 'My Line Chart Race Title', 714 | 'size': 18, 715 | 'color': 'red', 716 | 'loc': 'right', 717 | 'pad': 12 718 | } 719 | 720 | line_label_font : number or dict, default None 721 | Font size of labels at the end of the lines. When None, defaults to 7. 722 | Use a dictionary to supply several font properties. 723 | 724 | tick_label_font : number or dict, default None 725 | Font size of tick labels. When None, defaults to 7. 726 | Use a dictionary to supply several font properties. 727 | 728 | tick_template : str or function, default '{x:,.0f}' 729 | Formats the ticks on the y-axis with numeric values When given a 730 | string, it's passed to the ticker.StrMethodFormatter function. 731 | Use 'x' as the variable 732 | Example: '{x:10.2f}' 733 | 734 | WHen given a function, it's passed to ticker.FuncFormatter, which 735 | implicitly passes it two parameters `x` and `pos` and must return 736 | a string. 737 | 738 | shared_fontdict : dict, default None 739 | Dictionary of font properties shared across the tick labels, 740 | line labels, and title. The only property not shared is `size`. 741 | It will be ignored if you try to set it. 742 | Possible keys are: 743 | 'family', 'weight', 'color', 'style', 'stretch', 'weight', 'variant' 744 | Example: 745 | { 746 | 'family' : 'Helvetica', 747 | 'weight' : 'bold', 748 | 'color' : 'rebeccapurple' 749 | } 750 | 751 | scale : 'linear' or 'log', default 'linear' 752 | Type of scaling to use for the y-axis 753 | 754 | fig : matplotlib Figure, default None 755 | For greater control over the aesthetics, supply your own figure 756 | with at least one axes. 757 | 758 | writer : str or matplotlib Writer instance 759 | This argument is passed to the matplotlib FuncAnimation.save method. 760 | 761 | By default, the writer will be 'ffmpeg' unless creating a gif, 762 | then it will be 'imagemagick', or an html file, then it 763 | will be 'html'. 764 | 765 | Find all of the availabe Writers: 766 | >>> from matplotlib import animation 767 | >>> animation.writers.list() 768 | 769 | line_kwargs : dict, default None 770 | Other keyword arguments within a dictionary used to control line 771 | properties. 772 | Sample properties: 773 | `lw` - Line width, default is 1.5 774 | 'ls' - Line style, '-', '--', '-.', ':' 775 | `alpha` - opacity of line, 0 to 1 776 | 777 | fig_kwargs : dict, default None 778 | A dictionary of keyword arguments passed to the matplotlib 779 | Figure constructor. If not given, figsize is set to (6, 3.5) and 780 | dpi to 144. 781 | Example: 782 | { 783 | 'figsize': (8, 3), 784 | 'dpi': 120, 785 | 'facecolor': 'red' 786 | } 787 | 788 | Returns 789 | ------- 790 | When `filename` is left as `None`, an HTML5 video is returned as a string. 791 | Otherwise, a file of the animation is saved and `None` is returned. 792 | 793 | Examples 794 | -------- 795 | Use the `load_data` function to get an example dataset to 796 | create an animation. 797 | 798 | df = bcr.load_dataset('covid19') 799 | bcr.line_chart_race( 800 | df=df, 801 | filename='covid19_line_race.mp4', 802 | n_lines=5, 803 | steps_per_period=10, 804 | period_length=500, 805 | end_period_pause=300, 806 | period_summary_func=lambda v, r: {'x': .85, 'y': .2, 807 | 's': f'Total deaths: {v.sum()}', 808 | 'ha': 'right', 'size': 11}, 809 | line_width_data=None, 810 | agg_line_func='median', 811 | agg_line_kwargs=None 812 | others_line_func=None, 813 | others_line_kwargs=None, 814 | fade=.99, 815 | min_fade=.5, 816 | colors='dark12', 817 | title='COVID-19 Deaths by Country', 818 | line_label_font=7, 819 | tick_label_font=7, 820 | tick_template='{x:,.0f}' 821 | shared_fontdict={'family' : 'Helvetica', 'weight' : 'bold', 'color' : '.1'}, 822 | scale='linear', 823 | fig=None, 824 | writer=None, 825 | line_kwargs={'alpha': .7}, 826 | fig_kwargs={'figsizse': (6, 3.5), 'dpi': 144}) 827 | 828 | Font Help 829 | --------- 830 | Font size can also be a string - 'xx-small', 'x-small', 'small', 831 | 'medium', 'large', 'x-large', 'xx-large', 'smaller', 'larger' 832 | These sizes are relative to plt.rcParams['font.size']. 833 | ''' 834 | 835 | lcr = _LineChartRace(df, filename, n_lines, steps_per_period, period_length, end_period_pause, 836 | period_summary_func, line_width_data, agg_line_func, agg_line_kwargs, 837 | others_line_func, others_line_kwargs, fade, min_fade, images, colors, 838 | title, line_label_font, tick_label_font, tick_template, shared_fontdict, 839 | scale, fig, writer, line_kwargs, fig_kwargs) 840 | return lcr.make_animation() 841 | -------------------------------------------------------------------------------- /bar_chart_race/_pandas_accessor.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pandas as pd 4 | 5 | from ._bar_chart_race import bar_chart_race as bcr 6 | from ._line_chart_race import line_chart_race as lcr 7 | from ._utils import prepare_wide_data as pwd, prepare_long_data as pld 8 | 9 | 10 | @pd.api.extensions.register_dataframe_accessor("bcr") 11 | class _BCR: 12 | 13 | def __init__(self, df): 14 | self._df = df 15 | 16 | def bar_chart_race(self, filename=None, orientation='h', sort='desc', n_bars=None, 17 | fixed_order=False, fixed_max=False, steps_per_period=10, 18 | period_length=500, end_period_pause=0, interpolate_period=False, 19 | period_label=True, period_template=None, period_summary_func=None, 20 | perpendicular_bar_func=None, colors=None, title=None, bar_size=.95, 21 | bar_textposition='outside', bar_texttemplate='{x:,.0f}', 22 | bar_label_font=None, tick_label_font=None, tick_template='{x:,.0f}', 23 | shared_fontdict=None, scale='linear', fig=None, writer=None, 24 | bar_kwargs=None, fig_kwargs=None, filter_column_colors=False): 25 | 26 | return bcr(self._df, filename, orientation, sort, n_bars, fixed_order, fixed_max, 27 | steps_per_period, period_length, end_period_pause, interpolate_period, 28 | period_label, period_template, period_summary_func, perpendicular_bar_func, 29 | colors, title, bar_size, bar_textposition, bar_texttemplate, 30 | bar_label_font, tick_label_font, tick_template, shared_fontdict, scale, 31 | fig, writer, bar_kwargs, fig_kwargs, filter_column_colors) 32 | 33 | def line_chart_race(self, filename=None, n_lines=None, steps_per_period=10, 34 | period_length=500, end_period_pause=0, period_summary_func=None, 35 | line_width_data=None, agg_line_func=None, agg_line_kwargs=None, 36 | others_line_func=None, others_line_kwargs=None, fade=1, min_fade=.3, 37 | images=None, colors=None, title=None, line_label_font=None, 38 | tick_label_font=None, tick_template='{x:,.0f}', shared_fontdict=None, 39 | scale='linear', fig=None, writer=None, line_kwargs=None, 40 | fig_kwargs=None): 41 | return lcr(self._df, filename, n_lines, steps_per_period, period_length, end_period_pause, 42 | period_summary_func, line_width_data, agg_line_func, agg_line_kwargs, 43 | others_line_func, others_line_kwargs, fade, min_fade, images, colors, 44 | title, line_label_font, tick_label_font, tick_template, shared_fontdict, 45 | scale, fig, writer, line_kwargs, fig_kwargs) 46 | 47 | def prepare_wide_data(self, orientation='h', sort='desc', n_bars=None, interpolate_period=False, 48 | steps_per_period=10, compute_ranks=True): 49 | return pwd(self._df, orientation, sort, n_bars, interpolate_period, 50 | steps_per_period, compute_ranks) 51 | 52 | def prepare_long_data(self, index, columns, values, aggfunc='sum', orientation='h', 53 | sort='desc', n_bars=None, interpolate_period=False, 54 | steps_per_period=10, compute_ranks=True): 55 | return pld(self._df, index, columns, values, aggfunc, orientation, 56 | sort, n_bars, interpolate_period, steps_per_period, compute_ranks) 57 | 58 | 59 | _BCR.bar_chart_race.__doc__ = re.sub('df : .*(?=filename :)', '', bcr.__doc__, flags=re.S) 60 | _BCR.line_chart_race.__doc__ = re.sub('df : .*(?=filename :)', '', lcr.__doc__, flags=re.S) 61 | _BCR.prepare_wide_data.__doc__ = re.sub('df : .*(?=filename :)', '', pwd.__doc__, flags=re.S) 62 | _BCR.prepare_long_data.__doc__ = re.sub('df : .*(?=filename :)', '', pld.__doc__, flags=re.S) 63 | 64 | import importlib 65 | if importlib.util.find_spec('plotly'): 66 | from ._bar_chart_race_plotly import bar_chart_race_plotly as bcrp 67 | def bar_chart_race_plotly(self, filename=None, orientation='h', sort='desc', n_bars=None, 68 | fixed_order=False, fixed_max=False, steps_per_period=10, 69 | period_length=500, end_period_pause=0, interpolate_period=False, 70 | period_label=True, period_template=None, period_summary_func=None, 71 | perpendicular_bar_func=None, colors=None, title=None, bar_size=.95, 72 | bar_textposition='outside', bar_texttemplate=None, bar_label_font=None, 73 | tick_label_font=None, hovertemplate=None, slider=True, scale='linear', 74 | bar_kwargs=None, layout_kwargs=None, write_html_kwargs=None, 75 | filter_column_colors=False): 76 | 77 | return bcrp(self._df, filename, orientation, sort, n_bars, fixed_order, fixed_max, 78 | steps_per_period, period_length, end_period_pause, interpolate_period, 79 | period_label, period_template, period_summary_func, perpendicular_bar_func, 80 | colors, title, bar_size, bar_textposition, bar_texttemplate, bar_label_font, 81 | tick_label_font, hovertemplate, slider, scale, bar_kwargs, layout_kwargs, 82 | write_html_kwargs, filter_column_colors) 83 | 84 | setattr(_BCR, 'bar_chart_race_plotly', bar_chart_race_plotly) 85 | _BCR.bar_chart_race_plotly.__doc__ = re.sub('df : .*(?=filename :)', '', bcrp.__doc__, flags=re.S) -------------------------------------------------------------------------------- /bar_chart_race/_utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | from matplotlib import image as mimage 5 | 6 | 7 | def load_dataset(name='covid19'): 8 | ''' 9 | Return a pandas DataFrame suitable for immediate use in `bar_chart_race`. 10 | Must be connected to the internet 11 | 12 | Parameters 13 | ---------- 14 | name : str, default 'covid19' 15 | Name of dataset to load from the bar_chart_race github repository. 16 | Choices include: 17 | * 'covid19' 18 | * 'covid19_tutorial' 19 | * 'urban_pop' 20 | * 'baseball' 21 | 22 | Returns 23 | ------- 24 | pandas DataFrame 25 | ''' 26 | url = f'https://raw.githubusercontent.com/dexplo/bar_chart_race/master/data/{name}.csv' 27 | 28 | index_dict = {'covid19_tutorial': 'date', 29 | 'covid19': 'date', 30 | 'urban_pop': 'year', 31 | 'baseball': None} 32 | index_col = index_dict[name] 33 | parse_dates = [index_col] if index_col else None 34 | return pd.read_csv(url, index_col=index_col, parse_dates=parse_dates) 35 | 36 | def prepare_wide_data(df, orientation='h', sort='desc', n_bars=None, interpolate_period=False, 37 | steps_per_period=10, compute_ranks=True): 38 | ''' 39 | Prepares 'wide' data for bar chart animation. 40 | Returns two DataFrames - the interpolated values and the interpolated ranks 41 | 42 | There is no need to use this function directly to create the animation. 43 | You can pass your DataFrame directly to `bar_chart_race`. 44 | 45 | This function is useful if you want to view the prepared data without 46 | creating an animation. 47 | 48 | Parameters 49 | ---------- 50 | df : pandas DataFrame 51 | Must be a 'wide' pandas DataFrame where each row represents a 52 | single period of time. 53 | Each column contains the values of the bars for that category. 54 | Optionally, use the index to label each time period. 55 | 56 | orientation : 'h' or 'v', default 'h' 57 | Bar orientation - horizontal or vertical 58 | 59 | sort : 'desc' or 'asc', default 'desc' 60 | Choose how to sort the bars. Use 'desc' to put largest bars on 61 | top and 'asc' to place largest bars on bottom. 62 | 63 | n_bars : int, default None 64 | Choose the maximum number of bars to display on the graph. 65 | By default, use all bars. New bars entering the race will 66 | appear from the bottom or top. 67 | 68 | interpolate_period : bool, default `False` 69 | Whether to interpolate the period. Only valid for datetime or 70 | numeric indexes. When set to `True`, for example, 71 | the two consecutive periods 2020-03-29 and 2020-03-30 with 72 | `steps_per_period` set to 4 would yield a new index of 73 | 2020-03-29 00:00:00 74 | 2020-03-29 06:00:00 75 | 2020-03-29 12:00:00 76 | 2020-03-29 18:00:00 77 | 2020-03-30 00:00:00 78 | 79 | steps_per_period : int, default 10 80 | The number of steps to go from one time period to the next. 81 | The bars will grow linearly between each period. 82 | 83 | compute_ranks : bool, default True 84 | When `True` return both the interpolated values and ranks DataFrames 85 | Otherwise just return the values 86 | 87 | Returns 88 | ------- 89 | A tuple of DataFrames. The first is the interpolated values and the second 90 | is the interpolated ranks. 91 | 92 | Examples 93 | -------- 94 | df_values, df_ranks = bcr.prepare_wide_data(df) 95 | ''' 96 | if n_bars is None: 97 | n_bars = df.shape[1] 98 | 99 | df_values = df.reset_index() 100 | df_values.index = df_values.index * steps_per_period 101 | new_index = range(df_values.index[-1] + 1) 102 | df_values = df_values.reindex(new_index) 103 | if interpolate_period: 104 | if df_values.iloc[:, 0].dtype.kind == 'M': 105 | first, last = df_values.iloc[[0, -1], 0] 106 | dr = pd.date_range(first, last, periods=len(df_values)) 107 | df_values.iloc[:, 0] = dr 108 | else: 109 | df_values.iloc[:, 0] = df_values.iloc[:, 0].interpolate() 110 | else: 111 | df_values.iloc[:, 0] = df_values.iloc[:, 0].fillna(method='ffill') 112 | 113 | df_values = df_values.set_index(df_values.columns[0]) 114 | if compute_ranks: 115 | df_ranks = df_values.rank(axis=1, method='first', ascending=False).clip(upper=n_bars + 1) 116 | if (sort == 'desc' and orientation == 'h') or (sort == 'asc' and orientation == 'v'): 117 | df_ranks = n_bars + 1 - df_ranks 118 | df_ranks = df_ranks.interpolate() 119 | 120 | df_values = df_values.interpolate() 121 | if compute_ranks: 122 | return df_values, df_ranks 123 | return df_values 124 | 125 | def prepare_long_data(df, index, columns, values, aggfunc='sum', orientation='h', 126 | sort='desc', n_bars=None, interpolate_period=False, 127 | steps_per_period=10, compute_ranks=True): 128 | ''' 129 | Prepares 'long' data for bar chart animation. 130 | Returns two DataFrames - the interpolated values and the interpolated ranks 131 | 132 | You (currently) cannot pass long data to `bar_chart_race` directly. Use this function 133 | to create your wide data first before passing it to `bar_chart_race`. 134 | 135 | Parameters 136 | ---------- 137 | df : pandas DataFrame 138 | Must be a 'long' pandas DataFrame where one column contains 139 | the period, another the categories, and the third the values 140 | of each category for each period. 141 | 142 | This DataFrame will be passed to the `pivot_table` method to turn 143 | it into a wide DataFrame. It will then be passed to the 144 | `prepare_wide_data` function. 145 | 146 | index : str 147 | Name of column used for the time period. It will be placed in the index 148 | 149 | columns : str 150 | Name of column containing the categories for each time period. This column 151 | will get pivoted so that each unique value is a column. 152 | 153 | values : str 154 | Name of column holding the values for each time period of each category. 155 | This column will become the values of the resulting DataFrame 156 | 157 | aggfunc : str or aggregation function, default 'sum' 158 | String name of aggregation function ('sum', 'min', 'mean', 'max, etc...) 159 | or actual function (np.sum, np.min, etc...). 160 | Categories that have multiple values for the same time period must be 161 | aggregated for the animation to work. 162 | 163 | orientation : 'h' or 'v', default 'h' 164 | Bar orientation - horizontal or vertical 165 | 166 | sort : 'desc' or 'asc', default 'desc' 167 | Choose how to sort the bars. Use 'desc' to put largest bars on 168 | top and 'asc' to place largest bars on bottom. 169 | 170 | n_bars : int, default None 171 | Choose the maximum number of bars to display on the graph. 172 | By default, use all bars. New bars entering the race will 173 | appear from the bottom or top. 174 | 175 | interpolate_period : bool, default `False` 176 | Whether to interpolate the period. Only valid for datetime or 177 | numeric indexes. When set to `True`, for example, 178 | the two consecutive periods 2020-03-29 and 2020-03-30 with 179 | `steps_per_period` set to 4 would yield a new index of 180 | 2020-03-29 00:00:00 181 | 2020-03-29 06:00:00 182 | 2020-03-29 12:00:00 183 | 2020-03-29 18:00:00 184 | 2020-03-30 00:00:00 185 | 186 | steps_per_period : int, default 10 187 | The number of steps to go from one time period to the next. 188 | The bars will grow linearly between each period. 189 | 190 | compute_ranks : bool, default True 191 | When `True` return both the interpolated values and ranks DataFrames 192 | Otherwise just return the values 193 | 194 | Returns 195 | ------- 196 | A tuple of DataFrames. The first is the interpolated values and the second 197 | is the interpolated ranks. 198 | 199 | Examples 200 | -------- 201 | df_values, df_ranks = bcr.prepare_long_data(df) 202 | bcr.bar_chart_race(df_values, steps_per_period=1, period_length=50) 203 | ''' 204 | df_wide = df.pivot_table(index=index, columns=columns, values=values, 205 | aggfunc=aggfunc).fillna(method='ffill') 206 | return prepare_wide_data(df_wide, orientation, sort, n_bars, interpolate_period, 207 | steps_per_period, compute_ranks) 208 | 209 | 210 | def read_images(filename, columns): 211 | image_dict = {} 212 | code_path = Path(__file__).resolve().parent / "_codes" 213 | code_value_path = code_path / 'code_value.csv' 214 | data_path = code_path / f'{filename}.csv' 215 | url_path = pd.read_csv(code_value_path).query('code == @filename')['value'].values[0] 216 | codes = pd.read_csv(data_path, index_col='code')['value'].to_dict() 217 | 218 | for col in columns: 219 | code = codes[col.lower()] 220 | if url_path == 'self': 221 | final_url = code 222 | else: 223 | final_url = url_path.format(code=code) 224 | image_dict[col] = mimage.imread(final_url) 225 | return image_dict -------------------------------------------------------------------------------- /data/baseball.csv: -------------------------------------------------------------------------------- 1 | name,year,hr 2 | Hank Aaron,0,0 3 | Barry Bonds,0,0 4 | Jimmie Foxx,0,0 5 | Ken Griffey,0,0 6 | Reggie Jackson,0,0 7 | Harmon Killebrew,0,0 8 | Mickey Mantle,0,0 9 | Willie Mays,0,0 10 | Willie McCovey,0,0 11 | Mark McGwire,0,0 12 | David Ortiz,0,0 13 | Rafael Palmeiro,0,0 14 | Albert Pujols,0,0 15 | Manny Ramirez,0,0 16 | Frank Robinson,0,0 17 | Alex Rodriguez,0,0 18 | Babe Ruth,0,0 19 | Mike Schmidt,0,0 20 | Sammy Sosa,0,0 21 | Jim Thome,0,0 22 | Hank Aaron,1,13 23 | Hank Aaron,2,40 24 | Hank Aaron,3,66 25 | Hank Aaron,4,110 26 | Hank Aaron,5,140 27 | Hank Aaron,6,179 28 | Hank Aaron,7,219 29 | Hank Aaron,8,253 30 | Hank Aaron,9,298 31 | Hank Aaron,10,342 32 | Hank Aaron,11,366 33 | Hank Aaron,12,398 34 | Hank Aaron,13,442 35 | Hank Aaron,14,481 36 | Hank Aaron,15,510 37 | Hank Aaron,16,554 38 | Hank Aaron,17,592 39 | Hank Aaron,18,639 40 | Hank Aaron,19,673 41 | Hank Aaron,20,713 42 | Hank Aaron,21,733 43 | Hank Aaron,22,745 44 | Hank Aaron,23,755 45 | Barry Bonds,1,16 46 | Barry Bonds,2,41 47 | Barry Bonds,3,65 48 | Barry Bonds,4,84 49 | Barry Bonds,5,117 50 | Barry Bonds,6,142 51 | Barry Bonds,7,176 52 | Barry Bonds,8,222 53 | Barry Bonds,9,259 54 | Barry Bonds,10,292 55 | Barry Bonds,11,334 56 | Barry Bonds,12,374 57 | Barry Bonds,13,411 58 | Barry Bonds,14,445 59 | Barry Bonds,15,494 60 | Barry Bonds,16,567 61 | Barry Bonds,17,613 62 | Barry Bonds,18,658 63 | Barry Bonds,19,703 64 | Barry Bonds,20,708 65 | Barry Bonds,21,734 66 | Barry Bonds,22,762 67 | Jimmie Foxx,1,0 68 | Jimmie Foxx,2,0 69 | Jimmie Foxx,3,3 70 | Jimmie Foxx,4,16 71 | Jimmie Foxx,5,49 72 | Jimmie Foxx,6,86 73 | Jimmie Foxx,7,116 74 | Jimmie Foxx,8,174 75 | Jimmie Foxx,9,222 76 | Jimmie Foxx,10,266 77 | Jimmie Foxx,11,302 78 | Jimmie Foxx,12,343 79 | Jimmie Foxx,13,379 80 | Jimmie Foxx,14,429 81 | Jimmie Foxx,15,464 82 | Jimmie Foxx,16,500 83 | Jimmie Foxx,17,519 84 | Jimmie Foxx,18,527 85 | Jimmie Foxx,19,527 86 | Jimmie Foxx,20,534 87 | Ken Griffey,1,16 88 | Ken Griffey,2,38 89 | Ken Griffey,3,60 90 | Ken Griffey,4,87 91 | Ken Griffey,5,132 92 | Ken Griffey,6,172 93 | Ken Griffey,7,189 94 | Ken Griffey,8,238 95 | Ken Griffey,9,294 96 | Ken Griffey,10,350 97 | Ken Griffey,11,398 98 | Ken Griffey,12,438 99 | Ken Griffey,13,460 100 | Ken Griffey,14,468 101 | Ken Griffey,15,481 102 | Ken Griffey,16,501 103 | Ken Griffey,17,536 104 | Ken Griffey,18,563 105 | Ken Griffey,19,593 106 | Ken Griffey,20,611 107 | Ken Griffey,21,630 108 | Ken Griffey,22,630 109 | Reggie Jackson,1,1 110 | Reggie Jackson,2,30 111 | Reggie Jackson,3,77 112 | Reggie Jackson,4,100 113 | Reggie Jackson,5,132 114 | Reggie Jackson,6,157 115 | Reggie Jackson,7,189 116 | Reggie Jackson,8,218 117 | Reggie Jackson,9,254 118 | Reggie Jackson,10,281 119 | Reggie Jackson,11,313 120 | Reggie Jackson,12,340 121 | Reggie Jackson,13,369 122 | Reggie Jackson,14,410 123 | Reggie Jackson,15,425 124 | Reggie Jackson,16,464 125 | Reggie Jackson,17,478 126 | Reggie Jackson,18,503 127 | Reggie Jackson,19,530 128 | Reggie Jackson,20,548 129 | Reggie Jackson,21,563 130 | Harmon Killebrew,1,0 131 | Harmon Killebrew,2,4 132 | Harmon Killebrew,3,9 133 | Harmon Killebrew,4,11 134 | Harmon Killebrew,5,11 135 | Harmon Killebrew,6,53 136 | Harmon Killebrew,7,84 137 | Harmon Killebrew,8,130 138 | Harmon Killebrew,9,178 139 | Harmon Killebrew,10,223 140 | Harmon Killebrew,11,272 141 | Harmon Killebrew,12,297 142 | Harmon Killebrew,13,336 143 | Harmon Killebrew,14,380 144 | Harmon Killebrew,15,397 145 | Harmon Killebrew,16,446 146 | Harmon Killebrew,17,487 147 | Harmon Killebrew,18,515 148 | Harmon Killebrew,19,541 149 | Harmon Killebrew,20,546 150 | Harmon Killebrew,21,559 151 | Harmon Killebrew,22,573 152 | Mickey Mantle,1,13 153 | Mickey Mantle,2,36 154 | Mickey Mantle,3,57 155 | Mickey Mantle,4,84 156 | Mickey Mantle,5,121 157 | Mickey Mantle,6,173 158 | Mickey Mantle,7,207 159 | Mickey Mantle,8,249 160 | Mickey Mantle,9,280 161 | Mickey Mantle,10,320 162 | Mickey Mantle,11,374 163 | Mickey Mantle,12,404 164 | Mickey Mantle,13,419 165 | Mickey Mantle,14,454 166 | Mickey Mantle,15,473 167 | Mickey Mantle,16,496 168 | Mickey Mantle,17,518 169 | Mickey Mantle,18,536 170 | Willie Mays,1,20 171 | Willie Mays,2,24 172 | Willie Mays,3,65 173 | Willie Mays,4,116 174 | Willie Mays,5,152 175 | Willie Mays,6,187 176 | Willie Mays,7,216 177 | Willie Mays,8,250 178 | Willie Mays,9,279 179 | Willie Mays,10,319 180 | Willie Mays,11,368 181 | Willie Mays,12,406 182 | Willie Mays,13,453 183 | Willie Mays,14,505 184 | Willie Mays,15,542 185 | Willie Mays,16,564 186 | Willie Mays,17,587 187 | Willie Mays,18,600 188 | Willie Mays,19,628 189 | Willie Mays,20,646 190 | Willie Mays,21,654 191 | Willie Mays,22,660 192 | Willie McCovey,1,13 193 | Willie McCovey,2,26 194 | Willie McCovey,3,44 195 | Willie McCovey,4,64 196 | Willie McCovey,5,108 197 | Willie McCovey,6,126 198 | Willie McCovey,7,165 199 | Willie McCovey,8,201 200 | Willie McCovey,9,232 201 | Willie McCovey,10,268 202 | Willie McCovey,11,313 203 | Willie McCovey,12,352 204 | Willie McCovey,13,370 205 | Willie McCovey,14,384 206 | Willie McCovey,15,413 207 | Willie McCovey,16,435 208 | Willie McCovey,17,458 209 | Willie McCovey,18,465 210 | Willie McCovey,19,493 211 | Willie McCovey,20,505 212 | Willie McCovey,21,520 213 | Willie McCovey,22,521 214 | Mark McGwire,1,3 215 | Mark McGwire,2,52 216 | Mark McGwire,3,84 217 | Mark McGwire,4,117 218 | Mark McGwire,5,156 219 | Mark McGwire,6,178 220 | Mark McGwire,7,220 221 | Mark McGwire,8,229 222 | Mark McGwire,9,238 223 | Mark McGwire,10,277 224 | Mark McGwire,11,329 225 | Mark McGwire,12,387 226 | Mark McGwire,13,457 227 | Mark McGwire,14,522 228 | Mark McGwire,15,554 229 | Mark McGwire,16,583 230 | David Ortiz,1,1 231 | David Ortiz,2,10 232 | David Ortiz,3,10 233 | David Ortiz,4,20 234 | David Ortiz,5,38 235 | David Ortiz,6,58 236 | David Ortiz,7,89 237 | David Ortiz,8,130 238 | David Ortiz,9,177 239 | David Ortiz,10,231 240 | David Ortiz,11,266 241 | David Ortiz,12,289 242 | David Ortiz,13,317 243 | David Ortiz,14,349 244 | David Ortiz,15,378 245 | David Ortiz,16,401 246 | David Ortiz,17,431 247 | David Ortiz,18,466 248 | David Ortiz,19,503 249 | David Ortiz,20,541 250 | Rafael Palmeiro,1,3 251 | Rafael Palmeiro,2,17 252 | Rafael Palmeiro,3,25 253 | Rafael Palmeiro,4,33 254 | Rafael Palmeiro,5,47 255 | Rafael Palmeiro,6,73 256 | Rafael Palmeiro,7,95 257 | Rafael Palmeiro,8,132 258 | Rafael Palmeiro,9,155 259 | Rafael Palmeiro,10,194 260 | Rafael Palmeiro,11,233 261 | Rafael Palmeiro,12,271 262 | Rafael Palmeiro,13,314 263 | Rafael Palmeiro,14,361 264 | Rafael Palmeiro,15,400 265 | Rafael Palmeiro,16,447 266 | Rafael Palmeiro,17,490 267 | Rafael Palmeiro,18,528 268 | Rafael Palmeiro,19,551 269 | Rafael Palmeiro,20,569 270 | Albert Pujols,1,37 271 | Albert Pujols,2,71 272 | Albert Pujols,3,114 273 | Albert Pujols,4,160 274 | Albert Pujols,5,201 275 | Albert Pujols,6,250 276 | Albert Pujols,7,282 277 | Albert Pujols,8,319 278 | Albert Pujols,9,366 279 | Albert Pujols,10,408 280 | Albert Pujols,11,445 281 | Albert Pujols,12,475 282 | Albert Pujols,13,492 283 | Albert Pujols,14,520 284 | Albert Pujols,15,560 285 | Albert Pujols,16,591 286 | Albert Pujols,17,614 287 | Albert Pujols,18,633 288 | Albert Pujols,19,656 289 | Manny Ramirez,1,2 290 | Manny Ramirez,2,19 291 | Manny Ramirez,3,50 292 | Manny Ramirez,4,83 293 | Manny Ramirez,5,109 294 | Manny Ramirez,6,154 295 | Manny Ramirez,7,198 296 | Manny Ramirez,8,236 297 | Manny Ramirez,9,277 298 | Manny Ramirez,10,310 299 | Manny Ramirez,11,347 300 | Manny Ramirez,12,390 301 | Manny Ramirez,13,435 302 | Manny Ramirez,14,470 303 | Manny Ramirez,15,490 304 | Manny Ramirez,16,527 305 | Manny Ramirez,17,546 306 | Manny Ramirez,18,555 307 | Manny Ramirez,19,555 308 | Frank Robinson,1,38 309 | Frank Robinson,2,67 310 | Frank Robinson,3,98 311 | Frank Robinson,4,134 312 | Frank Robinson,5,165 313 | Frank Robinson,6,202 314 | Frank Robinson,7,241 315 | Frank Robinson,8,262 316 | Frank Robinson,9,291 317 | Frank Robinson,10,324 318 | Frank Robinson,11,373 319 | Frank Robinson,12,403 320 | Frank Robinson,13,418 321 | Frank Robinson,14,450 322 | Frank Robinson,15,475 323 | Frank Robinson,16,503 324 | Frank Robinson,17,522 325 | Frank Robinson,18,552 326 | Frank Robinson,19,574 327 | Frank Robinson,20,583 328 | Frank Robinson,21,586 329 | Alex Rodriguez,1,0 330 | Alex Rodriguez,2,5 331 | Alex Rodriguez,3,41 332 | Alex Rodriguez,4,64 333 | Alex Rodriguez,5,106 334 | Alex Rodriguez,6,148 335 | Alex Rodriguez,7,189 336 | Alex Rodriguez,8,241 337 | Alex Rodriguez,9,298 338 | Alex Rodriguez,10,345 339 | Alex Rodriguez,11,381 340 | Alex Rodriguez,12,429 341 | Alex Rodriguez,13,464 342 | Alex Rodriguez,14,518 343 | Alex Rodriguez,15,553 344 | Alex Rodriguez,16,583 345 | Alex Rodriguez,17,613 346 | Alex Rodriguez,18,629 347 | Alex Rodriguez,19,647 348 | Alex Rodriguez,20,654 349 | Alex Rodriguez,21,687 350 | Alex Rodriguez,22,696 351 | Babe Ruth,1,0 352 | Babe Ruth,2,4 353 | Babe Ruth,3,7 354 | Babe Ruth,4,9 355 | Babe Ruth,5,20 356 | Babe Ruth,6,49 357 | Babe Ruth,7,103 358 | Babe Ruth,8,162 359 | Babe Ruth,9,197 360 | Babe Ruth,10,238 361 | Babe Ruth,11,284 362 | Babe Ruth,12,309 363 | Babe Ruth,13,356 364 | Babe Ruth,14,416 365 | Babe Ruth,15,470 366 | Babe Ruth,16,516 367 | Babe Ruth,17,565 368 | Babe Ruth,18,611 369 | Babe Ruth,19,652 370 | Babe Ruth,20,686 371 | Babe Ruth,21,708 372 | Babe Ruth,22,714 373 | Mike Schmidt,1,1 374 | Mike Schmidt,2,19 375 | Mike Schmidt,3,55 376 | Mike Schmidt,4,93 377 | Mike Schmidt,5,131 378 | Mike Schmidt,6,169 379 | Mike Schmidt,7,190 380 | Mike Schmidt,8,235 381 | Mike Schmidt,9,283 382 | Mike Schmidt,10,314 383 | Mike Schmidt,11,349 384 | Mike Schmidt,12,389 385 | Mike Schmidt,13,425 386 | Mike Schmidt,14,458 387 | Mike Schmidt,15,495 388 | Mike Schmidt,16,530 389 | Mike Schmidt,17,542 390 | Mike Schmidt,18,548 391 | Sammy Sosa,1,4 392 | Sammy Sosa,2,19 393 | Sammy Sosa,3,29 394 | Sammy Sosa,4,37 395 | Sammy Sosa,5,70 396 | Sammy Sosa,6,95 397 | Sammy Sosa,7,131 398 | Sammy Sosa,8,171 399 | Sammy Sosa,9,207 400 | Sammy Sosa,10,273 401 | Sammy Sosa,11,336 402 | Sammy Sosa,12,386 403 | Sammy Sosa,13,450 404 | Sammy Sosa,14,499 405 | Sammy Sosa,15,539 406 | Sammy Sosa,16,574 407 | Sammy Sosa,17,588 408 | Sammy Sosa,18,609 409 | Jim Thome,1,1 410 | Jim Thome,2,3 411 | Jim Thome,3,10 412 | Jim Thome,4,30 413 | Jim Thome,5,55 414 | Jim Thome,6,93 415 | Jim Thome,7,133 416 | Jim Thome,8,163 417 | Jim Thome,9,196 418 | Jim Thome,10,233 419 | Jim Thome,11,282 420 | Jim Thome,12,334 421 | Jim Thome,13,381 422 | Jim Thome,14,423 423 | Jim Thome,15,430 424 | Jim Thome,16,472 425 | Jim Thome,17,507 426 | Jim Thome,18,541 427 | Jim Thome,19,564 428 | Jim Thome,20,589 429 | Jim Thome,21,604 430 | Jim Thome,22,612 431 | -------------------------------------------------------------------------------- /data/covid19.csv: -------------------------------------------------------------------------------- 1 | date,Belgium,Brazil,Canada,China,France,Germany,India,Indonesia,Iran,Ireland,Italy,Mexico,Netherlands,Portugal,Spain,Sweden,Switzerland,Turkey,USA,United Kingdom 2 | 2020-02-26,,,,2717.0,2.0,,,,19.0,,12.0,,,,,,,,, 3 | 2020-02-27,,,,2746.0,2.0,,,,26.0,,17.0,,,,,,,,, 4 | 2020-02-28,,,,2790.0,2.0,,,,34.0,,21.0,,,,,,,,, 5 | 2020-02-29,,,,2837.0,2.0,,,,43.0,,29.0,,,,,,,,1.0, 6 | 2020-03-01,,,,2872.0,2.0,,,,54.0,,34.0,,,,,,,,1.0, 7 | 2020-03-02,,,,2914.0,3.0,,,,66.0,,52.0,,,,,,,,6.0, 8 | 2020-03-03,,,,2947.0,4.0,,,,77.0,,79.0,,,,1.0,,,,7.0, 9 | 2020-03-04,,,,2983.0,4.0,,,,92.0,,107.0,,,,2.0,,,,11.0, 10 | 2020-03-05,,,,3015.0,6.0,,,,107.0,,148.0,,,,3.0,,1.0,,12.0,1.0 11 | 2020-03-06,,,,3044.0,9.0,,,,124.0,,197.0,,1.0,,5.0,,1.0,,14.0,2.0 12 | 2020-03-07,,,,3072.0,11.0,,,,145.0,,233.0,,1.0,,10.0,,1.0,,17.0,2.0 13 | 2020-03-08,,,,3100.0,19.0,,,,194.0,,366.0,,3.0,,17.0,,2.0,,21.0,3.0 14 | 2020-03-09,,,1.0,3123.0,19.0,2.0,,,237.0,,463.0,,3.0,,28.0,,2.0,,22.0,4.0 15 | 2020-03-10,,,1.0,3139.0,33.0,2.0,,,291.0,,631.0,,4.0,,35.0,,3.0,,28.0,6.0 16 | 2020-03-11,3.0,,1.0,3161.0,48.0,3.0,1.0,1.0,354.0,1.0,827.0,,5.0,,54.0,1.0,4.0,,32.0,8.0 17 | 2020-03-12,3.0,,1.0,3172.0,48.0,3.0,1.0,1.0,429.0,1.0,1000.0,,5.0,,55.0,1.0,4.0,,40.0,8.0 18 | 2020-03-13,3.0,,1.0,3180.0,79.0,7.0,2.0,4.0,514.0,1.0,1266.0,,10.0,,133.0,1.0,11.0,,48.0,8.0 19 | 2020-03-14,4.0,,1.0,3193.0,91.0,9.0,2.0,5.0,611.0,2.0,1441.0,,12.0,,195.0,2.0,13.0,,54.0,21.0 20 | 2020-03-15,4.0,,1.0,3203.0,91.0,11.0,2.0,5.0,724.0,2.0,1809.0,,20.0,,289.0,3.0,14.0,,60.0,21.0 21 | 2020-03-16,5.0,,4.0,3217.0,149.0,17.0,2.0,5.0,853.0,2.0,2158.0,,24.0,,342.0,6.0,14.0,,84.0,56.0 22 | 2020-03-17,10.0,1.0,5.0,3230.0,149.0,24.0,3.0,5.0,988.0,2.0,2503.0,,43.0,1.0,533.0,7.0,27.0,1.0,107.0,56.0 23 | 2020-03-18,14.0,3.0,8.0,3241.0,149.0,28.0,3.0,19.0,1135.0,2.0,2978.0,,58.0,2.0,623.0,10.0,28.0,1.0,143.0,72.0 24 | 2020-03-19,21.0,6.0,9.0,3249.0,244.0,44.0,4.0,25.0,1284.0,3.0,3405.0,1.0,77.0,3.0,830.0,11.0,41.0,3.0,209.0,138.0 25 | 2020-03-20,37.0,11.0,12.0,3253.0,451.0,67.0,5.0,32.0,1433.0,3.0,4032.0,1.0,107.0,6.0,1043.0,16.0,54.0,4.0,260.0,178.0 26 | 2020-03-21,67.0,15.0,19.0,3259.0,563.0,84.0,6.0,38.0,1556.0,3.0,4825.0,2.0,137.0,12.0,1375.0,20.0,75.0,9.0,320.0,234.0 27 | 2020-03-22,75.0,25.0,21.0,3274.0,676.0,94.0,7.0,48.0,1685.0,4.0,5476.0,2.0,180.0,14.0,1772.0,21.0,98.0,30.0,427.0,282.0 28 | 2020-03-23,88.0,34.0,25.0,3274.0,862.0,123.0,10.0,49.0,1812.0,6.0,6077.0,3.0,214.0,23.0,2311.0,25.0,120.0,37.0,552.0,336.0 29 | 2020-03-24,122.0,46.0,26.0,3281.0,1102.0,157.0,10.0,55.0,1934.0,7.0,6820.0,4.0,277.0,33.0,2808.0,36.0,122.0,44.0,706.0,423.0 30 | 2020-03-25,178.0,59.0,30.0,3285.0,1333.0,206.0,12.0,58.0,2077.0,9.0,7503.0,5.0,357.0,43.0,3647.0,62.0,153.0,59.0,943.0,466.0 31 | 2020-03-26,220.0,77.0,38.0,3291.0,1698.0,267.0,20.0,78.0,2234.0,19.0,8215.0,6.0,435.0,60.0,4365.0,77.0,191.0,75.0,1210.0,580.0 32 | 2020-03-27,289.0,92.0,54.0,3296.0,1997.0,342.0,20.0,87.0,2378.0,22.0,9134.0,8.0,547.0,76.0,5138.0,105.0,231.0,92.0,1582.0,761.0 33 | 2020-03-28,353.0,111.0,61.0,3299.0,2317.0,433.0,24.0,102.0,2517.0,36.0,10023.0,12.0,640.0,100.0,5982.0,105.0,264.0,108.0,2182.0,1021.0 34 | 2020-03-29,431.0,136.0,64.0,3304.0,2611.0,533.0,27.0,114.0,2640.0,46.0,10779.0,16.0,772.0,119.0,6803.0,110.0,300.0,131.0,2566.0,1231.0 35 | 2020-03-30,513.0,159.0,80.0,3308.0,3030.0,645.0,32.0,122.0,2757.0,54.0,11591.0,20.0,865.0,140.0,7716.0,146.0,359.0,168.0,3112.0,1411.0 36 | 2020-03-31,705.0,201.0,101.0,3309.0,3532.0,775.0,35.0,136.0,2898.0,71.0,12428.0,28.0,1040.0,160.0,8464.0,180.0,433.0,214.0,4039.0,1793.0 37 | 2020-04-01,828.0,240.0,109.0,3316.0,4414.0,920.0,58.0,157.0,3036.0,85.0,13155.0,29.0,1175.0,187.0,9387.0,239.0,488.0,277.0,4995.0,2357.0 38 | 2020-04-02,1011.0,324.0,139.0,3322.0,5398.0,1107.0,72.0,170.0,3160.0,98.0,13915.0,37.0,1341.0,209.0,10348.0,308.0,536.0,356.0,6294.0,2926.0 39 | 2020-04-03,1143.0,359.0,179.0,3326.0,6520.0,1275.0,72.0,181.0,3294.0,120.0,14681.0,50.0,1490.0,246.0,11198.0,358.0,591.0,425.0,7418.0,3611.0 40 | 2020-04-04,1283.0,445.0,218.0,3330.0,7574.0,1444.0,86.0,191.0,3452.0,137.0,15362.0,60.0,1656.0,266.0,11947.0,373.0,666.0,501.0,8387.0,4320.0 41 | 2020-04-05,1447.0,486.0,259.0,3333.0,8093.0,1584.0,99.0,198.0,3603.0,158.0,15887.0,79.0,1771.0,295.0,12641.0,401.0,715.0,574.0,9489.0,4943.0 42 | 2020-04-06,1632.0,564.0,339.0,3335.0,8926.0,1810.0,136.0,209.0,3739.0,174.0,16523.0,94.0,1874.0,311.0,13341.0,477.0,765.0,649.0,10783.0,5385.0 43 | 2020-04-07,2035.0,686.0,375.0,3335.0,10343.0,2016.0,150.0,221.0,3872.0,210.0,17127.0,125.0,2108.0,345.0,14045.0,591.0,821.0,725.0,12798.0,6171.0 44 | 2020-04-08,2240.0,819.0,407.0,3337.0,10887.0,2349.0,178.0,240.0,3993.0,235.0,17669.0,141.0,2255.0,380.0,14792.0,687.0,895.0,812.0,14704.0,7111.0 45 | 2020-04-09,2523.0,950.0,503.0,3339.0,12228.0,2607.0,226.0,280.0,4110.0,263.0,18279.0,174.0,2403.0,409.0,15447.0,793.0,948.0,908.0,16553.0,7993.0 46 | 2020-04-10,3019.0,1057.0,557.0,3340.0,13215.0,2767.0,246.0,306.0,4232.0,287.0,18849.0,194.0,2520.0,435.0,16081.0,870.0,1002.0,1006.0,18595.0,8974.0 47 | 2020-04-11,3346.0,1124.0,654.0,3343.0,13851.0,2894.0,288.0,327.0,4357.0,320.0,19468.0,233.0,2653.0,470.0,16606.0,887.0,1036.0,1101.0,20471.0,9892.0 48 | 2020-04-12,3600.0,1223.0,714.0,3343.0,14412.0,3022.0,331.0,373.0,4474.0,334.0,19899.0,273.0,2747.0,504.0,17209.0,899.0,1106.0,1198.0,22032.0,10629.0 49 | 2020-04-13,3903.0,1328.0,779.0,3345.0,14986.0,3194.0,358.0,399.0,4585.0,365.0,20465.0,296.0,2833.0,535.0,17756.0,919.0,1138.0,1296.0,23546.0,11347.0 50 | 2020-04-14,4157.0,1532.0,899.0,3345.0,15748.0,3294.0,393.0,459.0,4683.0,406.0,21067.0,332.0,2955.0,567.0,18056.0,1033.0,1174.0,1403.0,25854.0,12129.0 51 | 2020-04-15,4440.0,1736.0,1006.0,3346.0,17188.0,3804.0,405.0,469.0,4777.0,444.0,21645.0,406.0,3145.0,599.0,18708.0,1203.0,1239.0,1518.0,28341.0,12894.0 52 | 2020-04-16,4857.0,1924.0,1257.0,3346.0,17941.0,4052.0,448.0,496.0,4869.0,486.0,22170.0,449.0,3327.0,629.0,19315.0,1333.0,1281.0,1643.0,32933.0,13759.0 53 | 2020-04-17,5163.0,2141.0,1354.0,4636.0,18703.0,4352.0,486.0,520.0,4958.0,530.0,22745.0,486.0,3471.0,657.0,20002.0,1400.0,1327.0,1769.0,36790.0,14607.0 54 | 2020-04-18,5453.0,2354.0,1399.0,4636.0,19345.0,4459.0,521.0,535.0,5031.0,571.0,23227.0,546.0,3613.0,687.0,20043.0,1511.0,1368.0,1890.0,38671.0,15498.0 55 | 2020-04-19,5683.0,2462.0,1563.0,4636.0,19744.0,4586.0,559.0,582.0,5118.0,610.0,23660.0,650.0,3697.0,714.0,20453.0,1540.0,1393.0,2017.0,40664.0,16095.0 56 | 2020-04-20,5828.0,2587.0,1725.0,4636.0,20292.0,4862.0,592.0,590.0,5209.0,687.0,24114.0,686.0,3764.0,735.0,20852.0,1580.0,1429.0,2140.0,42097.0,16550.0 57 | 2020-04-21,5998.0,2741.0,1908.0,4636.0,20829.0,5033.0,645.0,616.0,5297.0,730.0,24648.0,712.0,3929.0,762.0,21282.0,1765.0,1478.0,2259.0,44447.0,17378.0 58 | 2020-04-22,6262.0,2906.0,2075.0,4636.0,21373.0,5279.0,681.0,635.0,5391.0,769.0,25085.0,857.0,4068.0,785.0,21717.0,1937.0,1509.0,2376.0,46628.0,18151.0 59 | -------------------------------------------------------------------------------- /data/covid19_tutorial.csv: -------------------------------------------------------------------------------- 1 | date,Belgium,China,France,Germany,Iran,Italy,Netherlands,Spain,USA,United Kingdom 2 | 2020-04-03,1143,3326,6520,1275,3294,14681,1490,11198,7418,3611 3 | 2020-04-04,1283,3330,7574,1444,3452,15362,1656,11947,8387,4320 4 | 2020-04-05,1447,3333,8093,1584,3603,15887,1771,12641,9489,4943 5 | 2020-04-06,1632,3335,8926,1810,3739,16523,1874,13341,10783,5385 6 | 2020-04-07,2035,3335,10343,2016,3872,17127,2108,14045,12798,6171 7 | 2020-04-08,2240,3337,10887,2349,3993,17669,2255,14792,14704,7111 8 | 2020-04-09,2523,3339,12228,2607,4110,18279,2403,15447,16553,7993 9 | 2020-04-10,3019,3340,13215,2767,4232,18849,2520,16081,18595,8974 10 | 2020-04-11,3346,3343,13851,2894,4357,19468,2653,16606,20471,9892 11 | 2020-04-12,3600,3343,14412,3022,4474,19899,2747,17209,22032,10629 12 | -------------------------------------------------------------------------------- /data/urban_pop.csv: -------------------------------------------------------------------------------- 1 | year,United States,India,China,Ethiopia,Poland,Malaysia,Peru,Venezuela,Iraq,Saudi Arabia,Canada,Algeria,Ukraine,Vietnam,Thailand,"Congo, Dem. Rep.",Spain,South Africa,Colombia,Argentina,Egypt,South Korea,Italy,Philippines,France,United Kingdom,Bangladesh,Iran,Turkey,Germany,Pakistan,Nigeria,Mexico,Russia,Japan,Indonesia,Brazil 2 | 1976,160611122,138219074,162497601,3194879,19215135,4802814,9834645,10382196,7530612,4673719,17705309,6919237,28966085,9399461,10568872,6141742,25379947,12445371,15011687,21370520,17210387,17823527,36687131,15245564,39578989,43757644,7667521,15742290,16914583,56885943,18365687,13177979,38883279,90810675,85642808,26596457,67790415 3 | 1977,162256678,143699557,165293316,3300643,19625330,5038232,10197056,10778965,7901451,5041250,17923214,7196331,29452016,9652431,11092595,6381644,25880544,12771480,15524763,21797026,17668393,18743336,36955192,15826569,39798460,43833733,8543815,16521005,17474315,56801863,19175026,13868037,40371908,92479950,86538157,28001978,70478354 4 | 1978,164005080,149379782,171153535,3406129,20007316,5284698,10567468,11185526,8253727,5441027,18112108,7565661,29928920,9911702,11628473,6630163,26368356,13105135,16052408,22227673,18089504,19679806,37198851,16426578,40001620,43925435,9528585,17336704,18048189,56796181,20032626,14598364,41879098,94157479,87391419,29468690,73245834 5 | 1979,165847531,155285824,180399661,3522584,20341874,5539880,10945723,11600322,8598800,5885282,18301934,7956090,30386569,10181044,12176748,6882719,26838008,13454231,16593237,22668000,18530889,20636698,37418639,17048162,40205371,44054299,10622254,18227951,18640066,56865826,20941767,15355488,43407693,95659612,88197927,30996679,76091693 6 | 1980,167551171,161444128,189947471,3658252,20663601,5801267,11331194,12022351,8945814,6382806,18549289,8369497,30825447,10447627,12692095,7135884,27289444,13828615,17145845,23122595,18994489,21623805,37607540,17738316,40423470,44195960,11827261,19206467,19252680,57028530,21906732,16131172,44952217,96960865,88958689,32591870,79015954 7 | 1981,169552427,167521704,199949784,3817958,20985955,6050366,11723239,12450233,9294818,6937865,18791654,8805955,31287683,10720898,13071774,7389309,27669147,14253248,17710021,23609667,19480106,22617087,37764025,18687449,40659231,44271630,12920085,20274421,20329814,57229147,22897114,17103116,46412878,98228786,89733659,34436558,82013626 8 | 1982,171528659,173152676,210823843,4000296,21314754,6305685,12089571,12875246,9646517,7546375,19057229,9265778,31693551,11006067,13450801,7645260,27925417,14700152,18286697,24118025,19987197,23619800,37819718,19680770,40911627,44228171,13607076,21426582,21630562,57267174,23877291,18109529,47890690,99464693,90474900,36463770,85085296 9 | 1983,173459636,178956141,220472140,4203323,21651242,6571674,12460705,13303646,9998790,8194651,19287381,9746745,32110163,11301138,13832449,7907632,28151118,15162766,18874699,24636888,20516521,24621925,37822134,20717792,41177471,44215089,14326358,22654500,22977503,57114118,24901258,19156448,49383429,100677781,91224787,38577957,88209014 10 | 1984,175321738,184906540,230206255,4422527,21984900,6854613,12837778,13741349,10349519,8862080,19511294,10246073,32533314,11603770,14220724,8182628,28357570,15630401,19472129,25163825,21068787,25577374,37819273,21799606,41450438,44257473,15083858,23944608,24362121,56792461,25962081,20257934,50884710,102058393,91937389,40774954,91357900 11 | 1985,177239105,190975975,240414890,4655890,22299554,7158816,13220933,14189017,10697257,9530555,19731489,10759943,32933625,11912008,14616958,8520035,28549379,16134214,20077488,25695319,21643302,26473134,37818886,22923393,41726184,44329755,15880101,25281030,25769767,56485448,27052677,21421320,52390613,103466989,92632808,43042272,94505178 12 | 1986,179244877,197165614,251325056,4901173,22589785,7487770,13610395,14647328,11040738,10193888,19967235,11287827,33344880,12225169,15025684,8876843,28723937,16716155,20656600,26230730,22241759,27480866,37809627,24093004,42005647,44404206,16718788,26666054,27034322,56438249,28172326,22654337,53899557,104825013,93348378,45381922,97647144 13 | 1987,181215212,203469688,262976051,5160078,22855463,7839689,14005309,15114933,11382713,10774882,20237468,11821049,33766182,12541644,15443627,9253676,28887112,17299431,21228791,26769637,22812398,28537079,37802166,25307752,42289365,44470325,17598710,28011702,28238882,56695484,29321589,23956989,55410753,106169956,93963675,47793464,100778354 14 | 1988,183232441,209894838,275121076,5434867,23089202,8210275,14404448,15589603,11640586,11342799,20506939,12346640,34209429,12862589,15862003,9660136,29036437,17891635,21808613,27309085,23358966,29585374,37809110,26565345,42572024,44540723,18516964,29328931,29455664,57044790,30497993,25327851,56925349,107471421,94519909,50281090,103903008 15 | 1989,185333919,216442440,287504237,5728511,23241946,8591294,14805506,16067828,11877599,11895419,20883649,12878973,34541910,13237507,16267069,10107028,29169746,18505772,22398271,27844463,23893893,30626132,37826147,27860539,42846787,44628280,19464440,30587388,30682120,57469536,31695621,26761923,58445699,108424260,95061557,52840795,107023058 16 | 1990,187966119,223096279,300165618,6043927,23350476,8977771,15207438,16547195,12142167,12432320,21206427,13416507,34641542,13772504,16641681,10602701,29286916,19149881,23000000,28373007,24406147,31656393,37846480,29082060,43127028,44733264,20439396,31751090,31923300,58079842,32914428,28259055,59951345,108837430,95542280,55483475,110146163 17 | 1991,191509147,229752408,314301034,6383315,23450653,9366643,15608008,17014346,12437110,12952377,21482271,13958197,34734234,14324149,16936803,11155614,29430524,19822817,23613619,28887229,24890671,32459655,37861339,29760929,43467991,44855736,21391215,32799183,32891192,58625381,34148626,29472185,61411745,109073667,96005316,58355038,113265899 18 | 1992,195199459,236274335,328521540,6744717,23539562,9846036,16007183,17476497,12759904,13454748,21813814,14502589,34854630,14889784,17206294,11762423,29607126,20511177,24238652,29352243,25351104,33169705,37907512,30439167,43785894,45011752,22255441,33888647,33738148,59146205,35400161,30726608,62876086,109115423,96414127,61396542,116379447 19 | 1993,198806845,242896345,342961393,7125617,23616843,10348234,16406525,17938345,13107820,13888412,22130869,15044498,34894325,15462867,17462829,12400106,29793326,21212379,24870590,29809452,25795989,33872973,37964793,31119331,44077099,45154426,23136115,34949749,34589980,59647492,36682088,32024149,64340297,108985461,96821759,64522965,119489196 20 | 1994,202278113,249629427,357836540,7519515,23684074,10868160,16820251,18400682,13475501,14285427,22450573,15579364,34741407,16036816,17724400,13037939,29967941,21906214,25506165,30260590,26238204,34575766,38006634,31802962,44342923,45304828,24044693,35978707,35453793,60028206,38012967,33368626,65800649,108843023,97320876,67741187,122608542 21 | 1995,205718394,256470883,373035157,7888014,23733222,11409139,17240497,18863803,13858715,14663134,22760570,16103497,34487999,16604653,18004312,13652804,30133075,22576687,26142216,30705908,26687693,35280305,38041344,32491471,44604027,45459651,24983813,37010603,36333541,60378049,39405594,34764761,67251519,108866238,97862490,71046004,125743641 22 | 1996,209146726,263440887,388593258,8247227,23768665,11973524,17668537,19326875,14255733,15019607,23081461,16615709,34203211,17163390,18309040,14233459,30291556,23218617,26777679,31145811,27145811,35810625,38086153,33183963,44863823,45610451,25958281,38054098,37230469,60725922,40868934,36216363,68621427,108700578,98272808,74433222,128896593 23 | 1997,212694273,270523260,404485562,8610539,23801620,12557524,18100961,19788904,14665741,15360171,23428320,17115300,33912723,17713643,18632440,14788167,30451227,23836117,27410868,31579645,27681745,36259672,38140443,33879027,45123550,45762480,26961627,39051703,38142785,60986289,42394458,37721269,69944968,108515107,98667335,77897525,132175402 24 | 1998,216206090,277707327,420606126,8981735,23827143,13157335,18529226,20251502,15123544,15704580,23739358,17606301,33630036,18256423,18965132,15338240,30610090,24431488,28043543,32009566,28270163,36633453,38185563,34576876,45390693,45931122,27993765,40038693,39068844,61167004,43938667,39284672,71260342,108329743,99095072,81452538,135456370 25 | 1999,219721677,284978106,436766058,9363839,23842562,13764926,18940925,20715860,15606189,16078075,24048329,18103945,33333798,18823973,19293244,15915279,30767125,25011815,28676311,32438320,28862915,37006183,38226137,35277843,45743988,46119137,29046503,41030621,40002835,61376500,45445310,40910972,72568684,107987645,99434460,85098300,138709641 26 | 2000,223069137,292322757,452999147,9761536,23611695,14375105,19326872,21182664,16094906,16499665,24388404,18600197,33019123,19477364,19758316,16544508,30937864,25582579,29309135,32867357,29457843,37428328,38277624,35981496,46214923,46319551,30114488,42026541,40942334,61629857,46947759,42603693,73899942,107528575,99760751,88840036,141915773 27 | 2001,225792302,300118524,471767321,10174157,23622394,14918252,19683539,21651926,16594707,16978440,24757782,19094843,32707281,20134757,20680738,17234266,31186430,26143328,29941986,33297967,30055505,37867709,38333314,36689358,46709571,46557334,31346174,43031116,41943212,61902439,48435634,44726802,75268749,107067618,101706485,91738482,144508698 28 | 2002,228400290,308796507,491993700,10604081,23621395,15458510,20014065,22057235,17104294,17510539,25052940,19591211,32432088,20800243,21613066,17981229,31708814,26720456,30573543,33728934,30659220,38258247,38447500,37399541,47206863,46930583,32796275,44050010,42979004,62174878,49919690,46947855,76634718,106568907,104055019,94698443,147005321 29 | 2003,230876596,317584393,512473984,11049316,23563051,15999895,20323604,22460389,17607000,18084339,25304780,20095408,32238838,21474148,22554369,18783707,32390830,27305501,31200433,34159485,31267885,38626122,38686985,38104244,47699501,47323791,34276157,45078311,44016864,62376854,51408755,49272800,78013778,106132766,106256267,97720468,149452654 30 | 2004,233532722,326495067,533257098,11510093,23506503,16548747,20619818,22858285,18080764,18680566,25566897,20616005,32075858,22163653,23508150,19635486,33059302,27898739,31817837,34588886,31879899,38947802,39006818,38793193,48209404,47767769,35772637,46107032,45048098,62529997,52922901,51708640,79428213,105771150,108136910,100811250,151843988 31 | 2005,236200507,335503762,554367818,11986371,23453429,17108405,20909083,23248394,18513378,19286107,25834340,21159469,31932581,22870388,24472850,20534304,33727737,28506195,32422505,35015695,32495526,39195731,39267369,39459729,48730233,48269624,37274029,47130046,46065713,62660267,54474508,54260116,80890450,105433013,109856670,103961908,154176524 32 | 2006,238999326,344622641,575118254,12478999,23396235,17681869,21194852,23622801,18889524,19893608,26126316,21729732,31801166,23598140,25451682,21483246,34408810,29131012,33015422,35439470,33111793,39490771,39454178,40099208,49228316,48798541,38777278,48145863,47064036,62753557,56069495,56934076,82408287,105152847,111383848,107178769,156447985 33 | 2007,241795278,353850625,595670841,13001478,23340838,18266045,21477303,23979532,19223314,20507062,26441461,22327433,31694727,24348000,26439736,22487132,35159317,29774433,33597410,35861110,33700835,39740941,39722857,40715257,49690034,49351705,40283010,49125150,48048353,62833410,57704173,59734515,83973950,105037426,112827761,110459097,158660052 34 | 2008,244607104,363154577,616481190,13689470,23300939,18854125,21722213,24328099,19565443,21137735,26789863,22955890,31605915,25120981,27438708,23545867,35833174,30442138,34166611,36284569,34289703,40093884,40056298,41319488,50127513,49913475,41802031,50093529,49047262,62875807,59373384,62665438,85573769,105055515,114107975,113814309,160823508 35 | 2009,247276259,372465917,637407288,14413055,23274004,19435329,21951808,24683266,19985687,21801430,27158023,23612219,31547432,25920396,28447159,24661566,36260460,31137754,34720224,36714245,34919606,40351067,40308358,41929228,50547198,50463084,43352798,51080202,50096864,62877220,61070625,65723902,87182005,105149982,115228215,117243827,162949977 36 | 2010,249845296,381763166,658498663,15178365,23165018,20002882,22185851,25050752,20552598,22508632,27522537,24299173,31465465,26757120,29469051,25833935,36535850,31866171,35257426,37055902,35603056,40602657,40502481,42597021,50961407,51030310,44954427,52095517,51225589,62940432,62793242,68917190,88781439,105261473,116302928,120709130,165055094 37 | 2011,252186596,391040303,678933504,15986316,23134846,20516662,22421443,25452964,21282801,23264870,27847821,25016303,31395063,27621281,30179366,27065209,36773882,32630538,35770189,37543830,36347578,40909592,40641670,43505397,51373800,51600211,46610737,53140089,52440387,61940177,64539555,72230098,90370891,105407907,116416235,124016544,167158525 38 | 2012,254576561,400417728,699187267,16839218,23086831,21009012,22662393,25875071,22141872,24058860,28166078,25763106,31359984,28514086,30828051,28355103,36904876,33429132,36262940,38027774,37114895,41089082,40894259,44437841,51791144,52130345,48311928,54203628,53730762,62064608,66312625,75664328,91948801,105669949,116331281,127396459,169240750 39 | 2013,256899730,409909187,719587859,17717910,23025350,21491648,22922695,26249155,23075649,24865453,28479640,26536378,31330962,29430443,31477989,29704531,36891840,34249974,36761204,38509756,37919402,41240244,41548775,45385009,52228061,52650595,50048763,55292621,55071747,62242278,68114463,79214937,93515787,105998510,116262976,130826016,171300683 40 | 2014,259361210,419568459,740239259,18635946,22960228,21974918,23221633,26482983,24005089,25648738,28781576,27332602,31223156,30371288,32127193,31115109,36890017,35079618,37300597,38990109,38737018,41463573,42109853,46329521,52647802,53209683,51818338,56418308,56438761,62510392,69956952,82878551,95072126,106354643,116208079,134287151,173346772 41 | 2015,261865944,429428653,761027100,19590313,22897449,22464989,23571246,26518336,24872481,26382755,29011826,28146511,31183824,31333193,32772699,32586918,36971015,35905874,37904385,39467043,39551544,41645542,42247229,47262079,53009026,53802927,53608403,57580319,57806283,63062064,71845558,86652532,96615320,106703732,116182717,137751865,175375436 42 | 2016,264366216,439498772,782199374,20581872,22849639,22964507,23979736,26313884,25661856,27057429,29357013,28977628,31122512,32314724,33415230,34121207,37112875,36724030,38589139,39940546,40359123,41774264,42351339,48177810,53321626,54382825,55420910,58781895,59175037,63592936,73782310,90531047,98145001,107050095,116145370,141210511,177386818 43 | 2017,266676634,449789369,803554542,21609845,22824769,23467810,24438508,25917344,26391343,27678169,29727992,29821745,31043768,33310316,34051250,35717778,37311863,37534797,39338952,40410674,41185809,41861498,42462869,49096983,53612472,54923317,57254681,60016579,60537696,63861626,75761710,94518555,99655905,107349518,116053379,144652795,179379301 44 | 2018,268720071,460295677,823827650,22678295,22806875,23973075,24921870,25465822,27085311,28255384,30169097,30670086,30946607,34317154,34678853,37376673,37587449,38339668,40105215,40877099,42030812,42038247,42559879,50027217,53879064,55426598,59107944,61266765,61857510,64096118,77810763,98611179,101149488,107539347,115920900,148084795,181335507 45 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | background-color: transparent; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | margin-left:0; 5 | margin-right:0; 6 | border:none; 7 | border-collapse: collapse; 8 | border-spacing:0; 9 | color:black; 10 | font-size:13px; 11 | table-layout:fixed; 12 | } 13 | thead { 14 | border-bottom:1px solid black;vertical-align:bottom; 15 | } 16 | tr, th, td { 17 | text-align:right; 18 | vertical-align: middle; 19 | padding:0.5em 0.5em; 20 | line-height:normal; 21 | white-space:normal; 22 | max-width:none; 23 | border:none; 24 | } 25 | th { 26 | font-weight:bold; 27 | text-align:left; 28 | } 29 | tbody tr:nth-child(odd){ 30 | background:#f5f5f5; 31 | } 32 | :link{ 33 | text-decoration:underline; 34 | } 35 | 36 | .vid { 37 | display: flex; 38 | justify-content: center; 39 | } 40 | .vid video { 41 | width: 85%; 42 | } -------------------------------------------------------------------------------- /docs/data_preparation.md: -------------------------------------------------------------------------------- 1 | # Data Preparation 2 | 3 | bar_chart_race exposes two functions, `prepare_wide_data` and `prepare_long_data` to transform pandas DataFrames to the correct form. 4 | 5 | ## Wide data 6 | 7 | To show how the `prepare_wide_data` function works, we'll read in the last three rows from the `covid19_tutorial` dataset. 8 | 9 | ```python 10 | df = bcr.load_dataset('covid19_tutorial').tail(3) 11 | df 12 | ``` 13 | 14 | {% include 'html/data_preparation_1.html' %} 15 | 16 | This format of data is sometimes known as 'wide' data since each column contains data that all represents the same thing (deaths). Each new country would add an additional column to the DataFrame, making it wider. This is the type of data that the `bar_chart_race` function requires. 17 | 18 | The `prepare_wide_data` function is what `bar_chart_race` calls internally, so it isn't necessary to use directly. However, it is available so that you can view and understand how the data gets prepared. To transition the bars smoothly from one time period to the next, both the length of the bars and position are changed linearly. Two DataFrames of the same shape are returned - one for the values and the other for the ranks. 19 | 20 | ```python 21 | df_values, df_ranks = bcr.prepare_wide_data(df, steps_per_period=4, 22 | orientation='h', sort='desc') 23 | ``` 24 | 25 | Below, we have the `df_values` DataFrame containing the length of each bar for each frame. A total of four rows now exist for each period. 26 | 27 | {% include 'html/data_preparation_2.html' %} 28 | 29 | The `df_ranks` DataFrame contains the numerical ranking of each country and is used for the position of the bar along the y-axis (or x-axis when veritcal). Notice that there are two sets of bars that switch places. 30 | 31 | {% include 'html/data_preparation_3.html' %} 32 | 33 | ### Don't use before animation 34 | 35 | There is no need to use this function before making the animation if you already have wide data. Pass the `bar_chart_race` function your original data. 36 | 37 | ## Long data 38 | 39 | 'Long' data is a format for data where all values of the same kind are stored in a single column. Take a look at the baseball data below, which contains the cumulative number of home runs each of the top 20 home run hitters accumulated by year. 40 | 41 | ```python 42 | df_baseball = bcr.load_dataset('baseball') 43 | df_baseball 44 | ``` 45 | 46 | {% include 'html/data_preparation_4.html' %} 47 | 48 | Name, year, and home runs are each in a single column, contrasting with the wide data, where each column had the same type of data. Long data must be converted to wide data by pivoting categorical column and placing the period in the index. The `prepare_long_data` provides this functionality. It simply uses the pandas `pivot_table` method to pivot (and potentially aggregate) the data before passing it to `prepare_wide_data`. The same two DataFrames are returned. 49 | 50 | ```python 51 | df_values, df_ranks = bcr.prepare_long_data(df_baseball, index='year', columns='name', 52 | values='hr', steps_per_period=5) 53 | df_values.head(16) 54 | ``` 55 | 56 | The linearly interpolated values for the first three seasons of each player: 57 | 58 | {% include 'html/data_preparation_5.html' %} 59 | 60 | The rankings change substantially during this time period. 61 | 62 | ```python 63 | df_ranks.head(16) 64 | ``` 65 | 66 | {% include 'html/data_preparation_6.html' %} 67 | 68 | ### Usage before animation 69 | 70 | If you wish to use this function before an animation, set `steps_per_period` to 1. 71 | 72 | ```python 73 | df_values, df_ranks = bcr.prepare_long_data(df_baseball, index='year', columns='name', 74 | values='hr', steps_per_period=1, 75 | orientation='h', sort='desc') 76 | 77 | def period_summary(values, ranks): 78 | top2 = values.nlargest(2) 79 | leader = top2.index[0] 80 | lead = top2.iloc[0] - top2.iloc[1] 81 | s = f'{leader} by {lead:.0f}' 82 | return {'s': s, 'x': .95, 'y': .07, 'ha': 'right', 'size': 8} 83 | 84 | bcr.bar_chart_race(df_values, period_length=1000, 85 | fixed_max=True, fixed_order=True, n_bars=10, 86 | figsize=(5, 3), period_fmt='Season {x:,.0f}', 87 | title='Top 10 Home Run Hitters by Season Played') 88 | ``` 89 | 90 | {% macro video(name) %} 91 | 92 | 93 | 94 | {% endmacro %} 95 | 96 | {{ video('prepare_long') }} -------------------------------------------------------------------------------- /docs/images/bcr_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexplo/bar_chart_race/5930c3138a597d30c7d345379f5c1329ba68a035/docs/images/bcr_notebook.png -------------------------------------------------------------------------------- /docs/images/covid19_horiz.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexplo/bar_chart_race/5930c3138a597d30c7d345379f5c1329ba68a035/docs/images/covid19_horiz.gif -------------------------------------------------------------------------------- /docs/images/prepare_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexplo/bar_chart_race/5930c3138a597d30c7d345379f5c1329ba68a035/docs/images/prepare_long.png -------------------------------------------------------------------------------- /docs/images/wide_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexplo/bar_chart_race/5930c3138a597d30c7d345379f5c1329ba68a035/docs/images/wide_data.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Bar Chart Race 2 | 3 | [](https://pypi.org/project/bar_chart_race) 4 | [](LICENSE) 5 | 6 | Make animated bar and line chart races in Python with matplotlib or plotly. 7 | 8 |  9 | 10 | {% include 'html/plotly1.html' %} 11 | 12 | ## Installation 13 | 14 | Install with either: 15 | 16 | * `pip install bar_chart_race` 17 | * `conda install -c conda-forge bar_chart_race` 18 | 19 | ## Quickstart 20 | 21 | Begin with a pandas DataFrame containing 'wide' data where: 22 | 23 | * Every row represents a single period of time 24 | * Each column holds the value for a particular category 25 | * The index contains the time component (optional) 26 | 27 | The data below is an example of properly formatted data. It shows total deaths from COVID-19 for several countries by date. 28 | 29 | {% include 'html/tutorial_1.html' %} 30 | 31 | ### Create bar and line chart races 32 | 33 | There are three core functions available to construct the animations. 34 | 35 | * `bar_chart_race` 36 | * `bar_chart_race_plotly` 37 | * `line_chart_race` 38 | 39 | The above animation was created with the help of matplotlib using the following call to `bar_chart_race`. 40 | 41 | ```python 42 | import bar_chart_race 43 | df = bcr.load_dataset('covid19_tutorial') 44 | df.bcr.bar_chart_race( 45 | filename='../docs/images/covid19_horiz.gif', 46 | orientation='h', 47 | sort='desc', 48 | n_bars=8, 49 | fixed_order=False, 50 | fixed_max=True, 51 | steps_per_period=20, 52 | period_length=500, 53 | end_period_pause=0, 54 | interpolate_period=False, 55 | period_label={'x': .98, 'y': .3, 'ha': 'right', 'va': 'center'}, 56 | period_template='%B %d, %Y', 57 | period_summary_func=lambda v, r: {'x': .98, 'y': .2, 58 | 's': f'Total deaths: {v.sum():,.0f}', 59 | 'ha': 'right', 'size': 11}, 60 | perpendicular_bar_func='median', 61 | colors='dark12', 62 | title='COVID-19 Deaths by Country', 63 | bar_size=.95, 64 | bar_textposition='inside', 65 | bar_texttemplate='{x:,.0f}', 66 | bar_label_font=7, 67 | tick_label_font=7, 68 | tick_template='{x:,.0f}', 69 | shared_fontdict=None, 70 | scale='linear', 71 | fig=None, 72 | writer=None, 73 | bar_kwargs={'alpha': .7}, 74 | fig_kwargs={'figsize': (6, 3.5), 'dpi': 144}, 75 | filter_column_colors=False) 76 | ``` 77 | 78 | ### Save animation to disk or embed into a Jupyter Notebook 79 | 80 | If you are working within a Jupyter Notebook, leave the `filename` as `None` and it will be automatically embedded into a Jupyter Notebook. 81 | 82 | ```python 83 | bcr.bar_chart_race(df=df, filename=None) 84 | ``` 85 | 86 | ### Customization 87 | 88 | There are many options to customize the bar chart race to get the animation you desire. Below, we have an animation where the maximum x-value and order of the bars are set for the entire duration. A custom summary label and perpendicular bar of median is also added. 89 | 90 | ```python 91 | def period_summary(values, ranks): 92 | top2 = values.nlargest(2) 93 | leader = top2.index[0] 94 | lead = top2.iloc[0] - top2.iloc[1] 95 | s = f'{leader} by {lead:.0f}' 96 | return {'s': s, 'x': .99, 'y': .03, 'ha': 'right', 'size': 8} 97 | 98 | df_baseball = bcr.load_dataset('baseball').pivot(index='year', 99 | columns='name', 100 | values='hr') 101 | df_baseball.bcr.bar_chart_race( 102 | period_length=1000, 103 | fixed_max=True, 104 | fixed_order=True, 105 | n_bars=10, 106 | period_summary_func=period_summary, 107 | period_label={'x': .99, 'y': .1}, 108 | period_template='Season {x:,.0f}', 109 | title='Top 10 Home Run Hitters by Season Played') 110 | ``` 111 | 112 |  113 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Install from pypi or conda-forge 4 | 5 | * `pip install bar_chart_race` 6 | * `conda install -c conda-forge bar_chart_race` 7 | 8 | ## Installing ffmpeg 9 | 10 | In order to save animations as mp4/m4v/mov/etc... files, you must [install ffmpeg][0], which allows for conversion to many different formats of video and audio. For macOS users, installation may be [easier using Homebrew][2]. 11 | 12 | After installation, ensure that `ffmpeg` has been added to your path by going to your command line and entering `ffmepg -version`. 13 | 14 | ## Install ImageMagick for animated gifs 15 | 16 | If you desire to create animated gifs, you'll need to [install ImageMagick][1]. Verify that it has been added to your path with `magick -version`. 17 | 18 | ## Dependencies 19 | 20 | Bar Chart Race requires that you have both matplotlib and pandas installed. 21 | 22 | [0]: https://www.ffmpeg.org/download.html 23 | [1]: https://imagemagick.org/ 24 | [2]: https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#ffmpegthroughHomebrew -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | bar_chart_race offers a wide range of inputs to customize the animation. On this page, we'll cover many of the available options. 4 | 5 | ## Data format 6 | 7 | The data you choose to animate as a bar chart race must be provided in a specific format. The data must be within a pandas DataFrame containing 'wide' data where: 8 | 9 | * Each row represents a single period of time 10 | * Each column holds the value for a particular category 11 | * The index contains the time component (optional) 12 | 13 | ### Example data 14 | 15 | Below, we have an example of properly formatted data that shows total deaths from COVID-19 for several countries by date. Each row represents a single day's worth of data. Each column represents a single country's deaths. The index contains the date. Any pandas DataFrame that conforms to this structure may be used to create a bar chart race. 16 | 17 | {% include 'html/tutorial_1.html' %} 18 | 19 | ## Basic bar chart races 20 | 21 | A single main function, `bar_chart_race`, exists to create the animations. Calling it with the defaults returns the animation as an HTML string. The `load_dataset` function is available to load sample DataFrames. If you are working within a Jupyter Notebook, it will automatically be embedded in the output as a video. 22 | 23 | ```python 24 | import bar_chart_race 25 | df = bcr.load_dataset('covid19_tutorial') 26 | df.bcr.bar_chart_race() 27 | ``` 28 | 29 | {% macro video(name) %} 30 | 31 | 32 | 33 | {% endmacro %} 34 | 35 | {{ video('basic_default') }} 36 | 37 | ### Vertical bars 38 | 39 | By default, bars are horizontal. Use the `orientation` parameter to switch to vertical. 40 | 41 | ```python 42 | df.bcr.bar_chart_race(orientation='v') 43 | ``` 44 | 45 | {{ video('basic_vert') }} 46 | 47 | ### Ascending bars 48 | 49 | By default, the bars are plotted in descending order. Change the order by setting `sort` to `'asc'`. 50 | 51 | ```python 52 | df.bcr.bar_chart_race(sort='asc') 53 | ``` 54 | 55 | {{ video('basic_asc') }} 56 | 57 | ### Limit the number of bars 58 | 59 | By default, all columns will be plotted. Use `n_bars` to limit the number. When limiting bars, the smallest bar can drop off the plot. 60 | 61 | ```python 62 | df.bcr.bar_chart_race(n_bars=6) 63 | ``` 64 | 65 | {{ video('basic_n_bars') }} 66 | 67 | ### Fix the order of the bars 68 | 69 | By default, the bars will be ordered. Set `fixed_order` to `True` or to a specific list of column names to keep the order the same throughout. 70 | 71 | ```python 72 | df.bcr.bar_chart_race(fixed_order=['Iran', 'USA', 'Italy', 'Spain', 'Belgium']) 73 | ``` 74 | 75 | {{ video('basic_fixed_order') }} 76 | 77 | ### Fix the maximum value 78 | 79 | By default, the maximum value of the axis moves with the largest bar. Set `fixed_max` to `True` to keep the maximum value equal to the largest overall value for all frames in the animation. 80 | 81 | ```python 82 | df.bcr.bar_chart_race(fixed_max=True) 83 | ``` 84 | 85 | {{ video('basic_fixed_max') }} 86 | 87 | ### Change animation smoothness 88 | 89 | By default, 10 frames are used to step from one period to the next. Increase/decrease the smoothness of the animation with `steps_per_period`. 90 | 91 | ```python 92 | df.bcr.bar_chart_race(steps_per_period=3) 93 | ``` 94 | 95 | {{ video('basic_steps') }} 96 | 97 | You may also change the amount of time per period with `period_length`, which is set to 500 milliseconds (half of a second) by default. 98 | 99 | ```python 100 | df.bcr.bar_chart_race(steps_per_period=20, period_length=200) 101 | ``` 102 | 103 | {{ video('basic_period_length') }} 104 | 105 | ### Interpolate the period 106 | 107 | By default, the label for each frame changes after the entire period has been plotted. Linearly interpolate the value for the period with `interpolate_period`. Below, every frame increases by 1 / 10 of a day (2 hours and 24 minutes). 108 | 109 | ```python 110 | df.bcr.bar_chart_race(interpolate_period=True) 111 | ``` 112 | 113 | {{ video('basic_interpolate') }} 114 | 115 | ## Plot properties 116 | 117 | Many properties of the plot can be set. 118 | 119 | * `figsize` - sets the figure size using matplotlib figure inches (default: `(6, 3.5)`) 120 | * `dpi` - controls the dots per square inch (default: `144`) 121 | * `label_bars` - whether to label the bar values with text (default: `True`) 122 | * `period_label` - dictionary of matplotlib text properties or boolean (default: `True`) 123 | * `title` - title of plot 124 | 125 | ```python 126 | df.bcr.bar_chart_race(figsize=(5, 3), dpi=100, label_bars=False, 127 | period_label={'x': .99, 'y': .1, 'ha': 'right', 'color': 'red'}, 128 | title='COVID-19 Deaths by Country') 129 | ``` 130 | 131 | {{ video('basic_props') }} 132 | 133 | ### Label sizes 134 | 135 | Control the size of labels with `bar_label_size`, `tick_label_size`, and `title_size`. 136 | 137 | ```python 138 | df.bcr.bar_chart_race(bar_label_size=4, tick_label_size=5, 139 | title='COVID-19 Deaths by Country', title_size='smaller') 140 | ``` 141 | 142 | {{ video('basic_label_size') }} 143 | 144 | ### Setting font properties 145 | 146 | Set font properties for all text objects with `shared_fontdict`. 147 | 148 | ```python 149 | df.bcr.bar_chart_race(title='COVID-19 Deaths by Country', 150 | shared_fontdict={'family': 'Helvetica', 'weight': 'bold', 151 | 'color': 'rebeccapurple'}) 152 | ``` 153 | 154 | {{ video('basic_shared_font') }} 155 | ### Customize bar properties 156 | 157 | Set `bar_kwargs` to a dictionary of keyword arguments forwarded to the matploblib `bar` function to control bar properties. 158 | 159 | ```python 160 | df.bcr.bar_chart_race(bar_kwargs={'alpha': .2, 'ec': 'black', 'lw': 3}) 161 | ``` 162 | 163 | {{ video('basic_bar_kwargs') }} 164 | 165 | ## Additional features 166 | 167 | There are several more additional features to customize the animation. 168 | 169 | ### Formatting the period 170 | 171 | Format the label of the period by setting `period_fmt` to a string with either a date directive or a new-style formatted string. 172 | 173 | ```python 174 | df.bcr.bar_chart_race(period_fmt='%b %-d, %Y') 175 | ``` 176 | 177 | {{ video('other_date_directive') }} 178 | 179 | ### Use numbers for the index instead of dates 180 | 181 | It's not necessary to have dates or times in the index of the DataFrame. Below, the index is dropped, which replaces it with integers beginning at 0. These are then interpolated and formatted. 182 | 183 | ```python 184 | bcr.bar_chart_race(df.reset_index(drop=True), interpolate_period=True, 185 | period_fmt='Index value - {x:.2f}') 186 | ``` 187 | 188 | {{ video('other_string_fmt') }} 189 | 190 | ### Add text summarizing the entire period 191 | 192 | Define a function that accepts two arguments, the values and ranks of the current period of data, and returns 193 | a dictionary that will be passed to the matplotlib `text` function. 194 | 195 | ```python 196 | def summary(values, ranks): 197 | total_deaths = int(round(values.sum(), -2)) 198 | s = f'Total Deaths - {total_deaths:,.0f}' 199 | return {'x': .99, 'y': .05, 's': s, 'ha': 'right', 'size': 8} 200 | 201 | df.bcr.bar_chart_race(period_summary_func=summary) 202 | ``` 203 | 204 | {{ video('other_summary') }} 205 | 206 | ## Add a perpendicular bar 207 | 208 | Add a single bar perpendicular to the main bars by defining a function that accepts two arguments, the values and ranks of the current period of data, and returns a single number, the position of the bar. You can use string names of aggregation functions that pandas understands. 209 | 210 | ```python 211 | df.bcr.bar_chart_race(perpendicular_bar_func='mean') 212 | ``` 213 | 214 | {{ video('other_perpendicular') }} 215 | 216 | An example with a user-defined function: 217 | 218 | ```python 219 | def func(values, ranks): 220 | return values.quantile(.9) 221 | df.bcr.bar_chart_race(perpendicular_bar_func=func) 222 | ``` 223 | 224 | {{ video('other_perpendicular_func') }} 225 | 226 | ## Bar colors 227 | 228 | By default, the `'dark12'` colormap is used, with 12 unique colors. This is a qualitative color map containing every other color from the 'dark24' colormap originally found from the [plotly express documentation](https://plotly.com/python/discrete-color/#color-sequences-in-plotly-express). All [matplotlib](https://matplotlib.org/tutorials/colors/colormaps.html) and [plotly](https://plotly.com/python/builtin-colorscales/) colormaps are available by name. The entire `'dark24'` colormap will be used by default when your DataFrame contains more than 12 columns. 229 | 230 | ```python 231 | df.bcr.bar_chart_race(cmap='antique') 232 | ``` 233 | 234 | {{ video('color_map') }} 235 | 236 | ### Reduce color repetition 237 | 238 | It is possible that some colors repeat in your animation, even if there are more colors in the colormap than bars in the animation. This will only happen if you set the `n_bars` parameter, as colors are assigned to each column upon. You'll get a warning advising you to set 239 | `filter_column_colors` to `True`, which will only assign colors to those bars appearing in the animation. 240 | 241 | The following example uses the Accent colormap which has 8 unique colors. The animation is set to have a maximum of 7 bars, but there are still repeating colors. 242 | 243 | ```python 244 | df.bcr.bar_chart_race(cmap='accent', n_bars=7) 245 | ``` 246 | 247 | !!! warning "`UserWarning`" 248 | Some of your columns never make an appearance in the animation. To reduce color repetition, set `filter_column_colors` to `True` 249 | 250 | {{ video('color_warning') }} 251 | 252 | Setting `filter_column_colors` to `True` will reduce the likelihood of repeating colors, but will still happen if the total number of unique bars is more than the number of colors in the colormap. 253 | 254 | ```python 255 | df.bcr.bar_chart_race(cmap='accent', n_bars=7, filter_column_colors=True) 256 | ``` 257 | 258 | {{ video('color_warning_fixed') }} 259 | 260 | ## Using your own figure 261 | 262 | If you want to highly customize the animation, set the `fig` parameter to a previously created figure. This figure must have at aleast one matplotlib axes created within it. 263 | 264 | ```python 265 | fig, ax = plt.subplots(figsize=(5, 2), dpi=120) 266 | ax.set_facecolor((0, 0, 1, .3)) 267 | df.bcr.bar_chart_race(n_bars=3, fig=fig) 268 | ``` 269 | 270 | {{ video('other_figure') }} 271 | 272 | ### With subplots 273 | 274 | It's possible to add an animation to a matplotlib figure containing multiple subplots. The first subplot will be used for the animation. 275 | 276 | ```python 277 | from matplotlib import dates 278 | fig, ax_array = plt.subplots(2, 2, figsize=(8, 4), dpi=120, tight_layout=True) 279 | ax1, ax2, ax3, ax4 = ax_array.flatten() 280 | fig.suptitle('Animation in First Axes', y=1) 281 | 282 | ax2.plot(df) 283 | ax2.xaxis.set_major_locator(dates.DayLocator([3, 7, 12])) 284 | ax3.bar(df.index, df.median(axis=1)) 285 | ax3.xaxis.set_major_locator(dates.DayLocator([3, 7, 12])) 286 | ax4.pie(df.iloc[-1], radius=1.5, labels=df.columns) 287 | 288 | df.bcr.bar_chart_race(n_bars=3, fig=fig) 289 | ``` 290 | 291 | {{ video('other_subplots') }} 292 | 293 | ## Saving the animation 294 | 295 | ### Default returned values 296 | 297 | By default, the video will be embedded into your Jupyter Notebook. If you are not in a Jupyter Notebook, but have IPython installed, an `HTML` object will be returned. Retrieve the underlying HTML with the `data` attribute. 298 | 299 | ```python 300 | html = bcr.bar_chart_race(df) 301 | html.data # very long string of HTML 302 | ``` 303 | 304 | If you do not have IPython installed, then a string of HTML will be returned directly. 305 | 306 | ### Saving to disk 307 | 308 | In order to save the animation to disk, use a string of the file name of where you'd like to save as the second argument. You'll need to [install ffmpeg](../installation#installing-ffmpeg) first in order to save the animation. Once installed, you'll be able to save the animation as a wide variety of formats (mp4, m4v, mov, etc...). To save the animation as a gif, install ImageMagick. 309 | 310 | ``` 311 | df.bcr.bar_chart_race('docs/videos/covid19.mp4', figsize=(5, 3)) 312 | ``` 313 | 314 | ### Matplotlib writer 315 | 316 | To customize the animation, set the `writer` parameter to a matplotlib `MovieWriter` object instance. -------------------------------------------------------------------------------- /docs/upcoming.md: -------------------------------------------------------------------------------- 1 | # Upcoming Features 2 | 3 | * Support for Plotly animations 4 | * Images within the bars 5 | * Names within bars (as opposed to just tick labels) 6 | * Tools to see colormaps 7 | 8 | ## Request a feature 9 | 10 | Request a feature by [creating an issue on github](https://github.com/dexplo/bar_chart_race/issues). 11 | -------------------------------------------------------------------------------- /docs/whats_new.md: -------------------------------------------------------------------------------- 1 | # What's New 2 | 3 | ## Version 0.2 4 | 5 | Upcoming release on July xx, 2020 6 | 7 | ### Major New Features 8 | 9 | * Plotly animated bar charts with `bar_chart_race_plotly` 10 | * Line chart races with `line_chart_race` 11 | 12 | #### Other enhancements 13 | 14 | * Integration directly into pandas DataFrames - `df.bcr.bar_chart_race` 15 | * Bar label position able to be specified ('outside', 'inside', or None) using new parameter `bar_textposition` 16 | * Bar label formatting possible with string or function using new parameter `bar_texttemplate` 17 | * Added `end_period_pause` parameter that creates a pause (in milliseconds) at the end of each period 18 | * Parameter `title`, in addition to a string, can also be a dictionary using `'label'` as the key for the title. Other keys may be used to control text properties 19 | * Removed parameters `figsize` and `dpi` in favor of `fig_kwargs` dictionary capable of taking all matplotlib `Figure` parameters 20 | * Figure background color able to be saved 21 | * Several parameters changed name and order 22 | 23 | ## Version 0.1 24 | 25 | Released June 1, 2020 26 | 27 | This is the first major release of bar_chart_race adding many features: 28 | 29 | * [Fixed bar position](../tutorial#fix-the-order-of-the-bars) 30 | * [Fixed max value](../tutorial#fix-the-maximum-value) 31 | * [Perpendicular bars](../tutorial#add-a-perpendicular-bar) 32 | * [Interpolation of the period](../tutorial#change-animation-smoothness) 33 | * [Formatting of the period label](../tutorial#formatting-the-period) 34 | * [Period label summary](../tutorial#add-text-summarizing-the-entire-period) 35 | * [Support for plotly colormaps](../tutorial#bar-colors) 36 | 37 | ## Version 0.0.1 38 | 39 | Released April 29, 2020 40 | 41 | Genesis of bar_chart_race capable of producing smoothly transitioning bars with matplotlib and pandas. -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Bar Chart Race 2 | site_description: Animated bar chart races in Python with the bar_chart_race package 3 | site_author: Ted Petrou 4 | site_url: https://www.dexplo.org/bar_chart_race 5 | repo_url: https://github.com/dexplo/bar_chart_race 6 | copyright: Copyright @ 2020 Ted Petrou 7 | google_analytics: 8 | - UA-119777567-7 9 | - dexplo.org 10 | 11 | theme: 12 | name: material 13 | favicon: images/favicon.ico 14 | custom_dir: docs/overrides 15 | features: 16 | - tabs 17 | 18 | nav: 19 | - Home: index.md 20 | - Installation: installation.md 21 | - What's New: whats_new.md 22 | - Tutorial: tutorial.md 23 | - Data Preparation: data_preparation.md 24 | - Upcoming Features: upcoming.md 25 | - API Reference: api.md 26 | - Dexplo Libraries: 27 | - Dexplo: https://www.dexplo.org 28 | 29 | extra_css: 30 | - css/style.css 31 | 32 | extra: 33 | social: 34 | - icon: fontawesome/brands/github-alt 35 | link: https://github.com/dexplo 36 | - icon: fontawesome/brands/twitter 37 | link: https://twitter.com/TedPetrou 38 | - icon: fontawesome/brands/linkedin 39 | link: https://linkedin.com/in/TedPetrou 40 | - icon: fontawesome/brands/youtube 41 | link: https://www.youtube.com/c/dunderdata 42 | - icon: fontawesome/brands/facebook 43 | link: https://www.facebook.com/dunderdata 44 | 45 | markdown_extensions: 46 | - admonition 47 | - toc: 48 | permalink: True 49 | - codehilite: 50 | guess_lang: false 51 | - pymdownx.superfences 52 | 53 | plugins: 54 | - search 55 | - macros 56 | - minify: 57 | minify_html: false 58 | 59 | extra_javascript: 60 | - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML 61 | - https://cdn.plot.ly/plotly-latest.min.js -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import re 3 | 4 | with open('bar_chart_race/__init__.py', 'r') as f: 5 | for line in f: 6 | if line.startswith('__version__'): 7 | version = line.split("'")[1] 8 | 9 | with open("README.md", "r") as fh: 10 | long_description = fh.read() 11 | 12 | setuptools.setup( 13 | name="bar_chart_race", 14 | version=version, 15 | author="Ted Petrou", 16 | author_email="petrou.theodore@gmail.com", 17 | description="Create animated bar chart races using matplotlib or plotly", 18 | long_description=long_description, 19 | long_description_content_type="text/markdown", 20 | keywords="visualization animation bar chart race matplotlib pandas plotly", 21 | url="https://github.com/dexplo/bar_chart_race", 22 | packages=setuptools.find_packages(), 23 | license='MIT', 24 | classifiers=[ 25 | "Programming Language :: Python :: 3", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | ], 29 | install_requires=["pandas>=0.24", "matplotlib>=3.1"], 30 | python_requires='>=3.6', 31 | include_package_data=True, 32 | ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexplo/bar_chart_race/5930c3138a597d30c7d345379f5c1329ba68a035/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/lcr_data.csv: -------------------------------------------------------------------------------- 1 | date,US,Brazil,United Kingdom,Mexico,Italy,India,France,Spain,Peru,Iran,Russia,Belgium,Chile,Germany,Colombia,Canada,South Africa,Netherlands,Pakistan,Sweden 2 | 2020-03-11,33,0,7,0,827,1,48,54,0,354,0,3,0,3,0,1,0,5,0,1 3 | 2020-03-31,5605,201,2430,29,12428,35,3532,8464,30,2898,17,705,12,775,16,101,5,1040,27,180 4 | 2020-04-20,43466,2587,19105,712,24114,592,20267,20852,445,5209,405,5828,139,4862,189,1727,58,3764,201,1580 5 | 2020-05-10,80855,11123,31954,3465,30560,2212,26383,26621,1889,6640,1915,8656,312,7569,463,4991,194,5459,706,3225 6 | 2020-05-30,104778,28834,38977,9779,33340,5185,28774,27125,4371,7734,4555,9453,997,8530,890,7159,643,5970,1483,4395 7 | 2020-06-19,119739,48954,42653,20394,34561,12948,29620,28315,7660,9392,7831,9695,4093,8887,2045,8408,1831,6100,3382,5105 8 | 2020-07-09,133290,69184,44687,33526,34926,21604,29982,28401,11314,12305,10826,9778,6682,9057,4714,8797,3720,6156,5058,5500 9 | -------------------------------------------------------------------------------- /tests/test_bar_charts.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import matplotlib.pyplot as plt 3 | from bar_chart_race import load_dataset, bar_chart_race 4 | 5 | 6 | df = load_dataset('covid19') 7 | df = df.iloc[-20:-16] 8 | df1 = df.reset_index(drop=True) 9 | 10 | 11 | class TestSimpleBC: 12 | 13 | def test_defaults(self): 14 | bar_chart_race(df) 15 | bar_chart_race(df, orientation='v') 16 | 17 | def test_sort(self): 18 | bar_chart_race(df, sort='asc') 19 | bar_chart_race(df, orientation='v', sort='asc') 20 | 21 | def test_nbars(self): 22 | bar_chart_race(df, sort='desc', n_bars=8) 23 | bar_chart_race(df, orientation='v', sort='desc', n_bars=8) 24 | 25 | def test_fixed_order(self): 26 | bar_chart_race(df, sort='asc', n_bars=8, fixed_order=True) 27 | bar_chart_race(df, fixed_order=['Iran', 'USA', 'Italy', 'Spain']) 28 | 29 | def test_fixed_max(self): 30 | bar_chart_race(df, fixed_max=True) 31 | 32 | def test_steps_per_period(self): 33 | bar_chart_race(df, sort='asc', steps_per_period=2) 34 | bar_chart_race(df, sort='asc', steps_per_period=30) 35 | 36 | def test_interpolate_period(self): 37 | bar_chart_race(df, interpolate_period=True, n_bars=8) 38 | 39 | def test_bar_size(self): 40 | bar_chart_race(df, n_bars=8, bar_size=.99) 41 | 42 | def test_period_label(self): 43 | bar_chart_race(df, n_bars=8, period_label=False) 44 | bar_chart_race(df, n_bars=8, period_label={'x': .99, 'y': .1, 'ha': 'right'}) 45 | 46 | def test_period_fmt(self): 47 | bar_chart_race(df, n_bars=8, period_template='%b %d, %Y') 48 | bar_chart_race(df1, n_bars=8, interpolate_period=True, period_template='{x: .2f}') 49 | 50 | def test_period_summary_func(self): 51 | def summary(values, ranks): 52 | total_deaths = int(round(values.sum(), -2)) 53 | s = f'Total Deaths - {total_deaths:,.0f}' 54 | return {'x': .99, 'y': .05, 's': s, 'ha': 'right', 'size': 8} 55 | 56 | bar_chart_race(df, n_bars=8, period_summary_func=summary) 57 | 58 | def test_perpendicular_bar_func(self): 59 | bar_chart_race(df, n_bars=8, perpendicular_bar_func='mean') 60 | def func(values, ranks): 61 | return values.quantile(.9) 62 | 63 | bar_chart_race(df, n_bars=8, perpendicular_bar_func=func) 64 | 65 | def test_period_length(self): 66 | bar_chart_race(df, n_bars=8, period_length=1200) 67 | 68 | def test_figsize(self): 69 | bar_chart_race(df, fig_kwargs={'figsize': (4, 2), 'dpi': 120}) 70 | 71 | def test_filter_column_colors(self): 72 | with pytest.warns(UserWarning): 73 | bar_chart_race(df, n_bars=6, sort='asc', colors='Accent') 74 | 75 | bar_chart_race(df, n_bars=6, sort='asc', colors='Accent', filter_column_colors=True) 76 | bar_chart_race(df, n_bars=6, colors=plt.cm.tab20.colors[:19]) 77 | 78 | def test_colors(self): 79 | bar_chart_race(df, colors=['red', 'blue'], filter_column_colors=True) 80 | 81 | with pytest.raises(KeyError): 82 | bar_chart_race(df, colors='adf') 83 | 84 | def test_title(self): 85 | bar_chart_race(df, n_bars=6, title='Great title') 86 | bar_chart_race(df, n_bars=6, title={'label': 'Great title', 'size':20}) 87 | 88 | def test_shared_fontdict(self): 89 | bar_chart_race(df, n_bars=6, shared_fontdict={'family': 'Courier New', 90 | 'weight': 'bold', 'color': 'teal'}) 91 | 92 | def test_scale(self): 93 | bar_chart_race(df, n_bars=6, scale='log') 94 | 95 | def test_save(self): 96 | bar_chart_race(df, 'tests/videos/test.mp4', n_bars=6) 97 | bar_chart_race(df, 'tests/videos/test.gif', n_bars=6, writer='imagemagick') 98 | bar_chart_race(df, 'tests/videos/test.html', n_bars=6) 99 | 100 | def test_writer(self): 101 | bar_chart_race(df, 'tests/videos/test.mpeg', n_bars=6, writer='imagemagick') 102 | 103 | def test_fig(self): 104 | fig, ax = plt.subplots(dpi=100) 105 | bar_chart_race(df, n_bars=6, fig=fig) 106 | 107 | def test_bar_kwargs(self): 108 | bar_chart_race(df, n_bars=6, bar_kwargs={'alpha': .2, 'ec': 'black', 'lw': 3}) 109 | -------------------------------------------------------------------------------- /tests/test_line_chart.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | import bar_chart_race as bcr 5 | 6 | 7 | df_race = pd.read_csv('tests/data/lcr_data.csv', index_col='date', parse_dates=['date']) 8 | s = pd.Series({'US': 330, 'United Kingdom': 65, 'Brazil': 220, 'Italy': 60, 'France': 126}) 9 | df_pop = df_race[s.index] / s 10 | 11 | 12 | class TestBasics: 13 | 14 | def test_default(self): 15 | bcr.line_chart_race(df_race.iloc[:, -5:]) 16 | 17 | def test_lines(self): 18 | bcr.line_chart_race(df_race, n_lines=8) 19 | 20 | def test_images(self): 21 | bcr.line_chart_race(df_race, n_lines=4, images='country') 22 | 23 | def test_others_line_func(self): 24 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=True) 25 | 26 | def test_steps_per_period(self): 27 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=True, steps_per_period=20) 28 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=True, steps_per_period=3) 29 | 30 | def test_period_length(self): 31 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=True, 32 | period_length=200, steps_per_period=30) 33 | 34 | def test_others_line_func_agg(self): 35 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func='mean', steps_per_period=5) 36 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func='mean', 37 | others_line_kwargs={'s': 'Mean Others', 'color':'.5', 'lw': 3}, steps_per_period=5) 38 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func='mean', steps_per_period=5) 39 | 40 | def test_others_line_func_udf(self): 41 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=lambda x: x.sum(), 42 | others_line_kwargs={'s': 'Sum Others', 'color':'.5', 'lw': 3}, steps_per_period=5) 43 | 44 | def test_agg_line_func(self): 45 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=lambda x: x.sum(), 46 | period_length=1000, others_line_kwargs={'s': 'Sum Others', 'lw': 3, 'ls': '--'}, 47 | agg_line_func='median', agg_line_kwargs={'s': 'Median All'}, 48 | steps_per_period=5) 49 | 50 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=lambda x: x.sum(), 51 | period_length=1000, others_line_kwargs={'s': 'Sum Others', 'lw': 3, 'ls': '--'}, 52 | agg_line_func='sum', agg_line_kwargs={'s': 'Sum All'}, 53 | steps_per_period=5) 54 | 55 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=lambda x: x.sum(), 56 | period_length=1000, end_period_pause=1000, 57 | others_line_kwargs={'s': 'Sum Others', 'lw': 3, 'ls': '--'}, 58 | agg_line_func='median', agg_line_kwargs={'s': 'Median Shown'}, 59 | steps_per_period=5) 60 | 61 | def test_period_summary_func(self): 62 | def psf(values): 63 | total = values.sum() 64 | s = f'Worldwide Deaths: {total:,.0f}' 65 | return {'x': .05, 'y': .85, 's': s, 'size': 10} 66 | 67 | bcr.line_chart_race(df_race, n_lines=4, images='country', others_line_func=lambda x: x.sum(), 68 | others_line_kwargs={'s': 'Sum Others', 'lw': 3, 'ls': '--'}, 69 | agg_line_func='sum', agg_line_kwargs={'s': 'Sum All'}, 70 | steps_per_period=5, period_summary_func=psf) 71 | 72 | def test_line_width_data(self): 73 | bcr.line_chart_race(df_race[df_pop.columns], n_lines=5, images='country', 74 | steps_per_period=5, line_width_data=df_pop) 75 | 76 | def test_fade(self): 77 | bcr.line_chart_race(df_race[df_pop.columns], images='country', 78 | steps_per_period=5, line_width_data=df_pop, fade=.9) 79 | 80 | bcr.line_chart_race(df_race[df_pop.columns], n_lines=5, images='country', 81 | steps_per_period=5, line_width_data=df_pop, fade=.8, min_fade=0) 82 | 83 | def test_images(self): 84 | url = 'https://icons.iconarchive.com/icons/wikipedia/flags/1024/US-United-States-Flag-icon.png' 85 | images = [url] * 5 86 | bcr.line_chart_race(df_race, n_lines=5, images=images, title='COVID-19 Deaths', 87 | steps_per_period=5, line_width_data=df_pop, fade=.9) 88 | 89 | def test_colors(self): 90 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, colors='tab20') 91 | 92 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, colors=plt.cm.Accent) 93 | 94 | def test_font(self): 95 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, line_label_font=5, 96 | tick_label_font=4) 97 | 98 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, 99 | line_label_font={'size': 9, 'color': 'red'}, tick_label_font=4) 100 | 101 | def test_tick_template(self): 102 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, 103 | tick_template=lambda x, pos: f'{x / 1000:.0f}k') 104 | 105 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, 106 | tick_template='deaths {x:.2f}') 107 | 108 | def test_scale(self): 109 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, scale='linear') 110 | 111 | def test_fig(self): 112 | fig = plt.Figure(figsize=(6, 3), facecolor='tan', dpi=120) 113 | fig.add_subplot(1, 2, 1) 114 | fig.add_subplot(1, 2, 2) 115 | bcr.line_chart_race(df_race, n_lines=5, images='country', steps_per_period=5, fig=fig) 116 | 117 | def test_line_kwargs(self): 118 | bcr.line_chart_race(df_race, n_lines=5, images='country', 119 | steps_per_period=5, line_kwargs={"ls": '--', 'lw': 2, 'alpha': .3}) 120 | 121 | def test_fig_kwargs(self): 122 | bcr.line_chart_race(df_race, n_lines=5, images='country', 123 | steps_per_period=5, fig_kwargs={'figsize': (4, 2), 'dpi': 100, 'facecolor': 'yellow'}) 124 | 125 | def test_videos(self): 126 | bcr.line_chart_race(df_race, 'tests/videos/lcr_yellow.mp4', n_lines=5, images='country', 127 | steps_per_period=5, fig_kwargs={'facecolor': 'yellow'}) 128 | 129 | bcr.line_chart_race(df_race, 'tests/videos/test_html.html', n_lines=5, images='country', 130 | steps_per_period=5) 131 | -------------------------------------------------------------------------------- /tests/test_plotly.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | import bar_chart_race as bcr 5 | import plotly.graph_objects as go 6 | import plotly 7 | from plotly.subplots import make_subplots 8 | from bar_chart_race import load_dataset, bar_chart_race_plotly 9 | 10 | 11 | df = load_dataset('covid19') 12 | df = df.iloc[-20:-10] 13 | df1 = df.reset_index(drop=True) 14 | 15 | 16 | def test_all(): 17 | bar_chart_race_plotly(df) 18 | 19 | df.bcr.bar_chart_race_plotly() 20 | 21 | bar_chart_race_plotly(df, sort='asc') 22 | 23 | bar_chart_race_plotly(df, orientation='v', slider=False) 24 | 25 | bar_chart_race_plotly(df, orientation='v', sort='asc', slider=False) 26 | 27 | bar_chart_race_plotly(df, sort='desc', n_bars=8) 28 | 29 | bar_chart_race_plotly(df, orientation='v', sort='desc', n_bars=8) 30 | 31 | bar_chart_race_plotly(df, sort='asc', n_bars=8, fixed_order=True) 32 | 33 | bar_chart_race_plotly(df, fixed_order=['Iran', 'USA', 'Italy', 'Spain'], period_label={'x': .95, 'y': .9}) 34 | 35 | bar_chart_race_plotly(df, fixed_max=True) 36 | 37 | bar_chart_race_plotly(df, fixed_max=True, orientation='v') 38 | 39 | bar_chart_race_plotly(df, sort='asc', steps_per_period=2) 40 | 41 | bar_chart_race_plotly(df, interpolate_period=True, n_bars=8) 42 | 43 | bar_chart_race_plotly(df, n_bars=8, textposition='inside') 44 | 45 | bar_chart_race_plotly(df, n_bars=8, bar_size=.99, layout_kwargs={'height': 800}) 46 | 47 | bar_chart_race_plotly(df, n_bars=8, period_label=False) 48 | 49 | bar_chart_race_plotly(df, n_bars=8, sort='asc', orientation='h', period_label={'bgcolor': 'orange', 50 | 'font': {'color': 'blue', 'size': 30}}) 51 | 52 | bar_chart_race_plotly(df, n_bars=8, period_template='%b %d, %Y') 53 | 54 | bar_chart_race_plotly(df1, n_bars=8, period_template='{x:.1f}', interpolate_period=True) 55 | 56 | bar_chart_race_plotly(df, n_bars=8, interpolate_period=False, bar_textposition='outside', 57 | period_length=500, steps_per_period=10, fixed_max=True) 58 | 59 | def summary(values, ranks): 60 | total_deaths = int(round(values.sum(), -2)) 61 | s = f'Total Deaths - {total_deaths:,.0f}' 62 | return {'x': .99, 'y': .05, 'text': s, 'align': 'right', 'size': 8} 63 | 64 | bar_chart_race_plotly(df, n_bars=8, period_summary_func=summary) 65 | 66 | bar_chart_race_plotly(df, n_bars=8, period_summary_func=summary, perpendicular_bar_func='mean') 67 | 68 | bar_chart_race_plotly(df, n_bars=8, period_summary_func=summary, perpendicular_bar_func='max', fixed_max=True) 69 | 70 | def func(values, ranks): 71 | return values.quantile(.9) 72 | 73 | bar_chart_race_plotly(df, n_bars=8, period_summary_func=summary, perpendicular_bar_func=func) 74 | 75 | bar_chart_race_plotly(df, n_bars=8, period_length=1200) 76 | 77 | bar_chart_race_plotly(df, n_bars=6, sort='asc', colors='Accent') 78 | 79 | bar_chart_race_plotly(df, n_bars=6, sort='asc', colors='Accent', filter_column_colors=True) 80 | 81 | 82 | bar_chart_race_plotly(df, n_bars=6, colors=plt.cm.tab20.colors[:19]) 83 | 84 | bar_chart_race_plotly(df, colors=['red', 'blue'], filter_column_colors=True) 85 | 86 | bar_chart_race_plotly(df, n_bars=6, title={'text':'Great title', 'font': {'size': 40}, 'x': .5}) 87 | 88 | bar_chart_race_plotly(df, n_bars=6, bar_label_font=8, tick_label_font=20) 89 | 90 | bar_chart_race_plotly(df, n_bars=6, bar_label_font={'size': 18, 'family': 'Courier New, monospace', 'color': 'red'}) 91 | 92 | bar_chart_race_plotly(df, n_bars=6, scale='log') 93 | 94 | bar_chart_race_plotly(df, 'test.html', n_bars=6, write_html_kwargs={'auto_play': False}) 95 | -------------------------------------------------------------------------------- /tests/test_prepare.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import bar_chart_race as bcr 3 | 4 | 5 | class TestLoadData: 6 | 7 | def test_load_urban_pop(self): 8 | bcr.load_dataset('urban_pop') 9 | 10 | def test_load_covid(self): 11 | bcr.load_dataset('covid19') 12 | 13 | 14 | # class TestPrepareWideData: 15 | 16 | # df_wide = pd.read_csv('data/covid_test.csv', index_col='date', parse_dates=['date']) 17 | 18 | # def test_prepare_wide_data(self): 19 | # df_wide_values, df_wide_ranks = bcr.prepare_wide_data(self.df_wide) 20 | # df_wide_values_ans = pd.read_csv('data/covid_test_values.csv', 21 | # index_col='date', parse_dates=['date']) 22 | # df_wide_ranks_ans = pd.read_csv('data/covid_test_ranks.csv', 23 | # index_col='date', parse_dates=['date']) 24 | # pd.testing.assert_frame_equal(df_wide_values, df_wide_values_ans) 25 | # pd.testing.assert_frame_equal(df_wide_ranks, df_wide_ranks_ans) 26 | -------------------------------------------------------------------------------- /tests/videos/README.md: -------------------------------------------------------------------------------- 1 | # Test videos saved here --------------------------------------------------------------------------------