From 991f384f441e0682dac60680560f5fcf6c92dfde Mon Sep 17 00:00:00 2001 From: Alix Damman Date: Fri, 7 Jun 2019 15:51:25 +0200 Subject: [PATCH 1/3] fixed error raised in tutorial_IO notebook because of changes introduced by PR #756 --- doc/source/tutorial/tutorial_IO.ipyml | 4 +- doc/source/tutorial/tutorial_IO.ipynb | 2132 ++++++++++++------------- 2 files changed, 1068 insertions(+), 1068 deletions(-) diff --git a/doc/source/tutorial/tutorial_IO.ipyml b/doc/source/tutorial/tutorial_IO.ipyml index 78967e63c..c6a7ecca2 100644 --- a/doc/source/tutorial/tutorial_IO.ipyml +++ b/doc/source/tutorial/tutorial_IO.ipyml @@ -523,7 +523,7 @@ cells: - code: | - read_hdf(filepath_hdf, key='deaths', sort_rows=True) + read_hdf(filepath_hdf, key='deaths').sort_axes() - markdown: | @@ -728,7 +728,7 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.8 + version: 3.7.3 livereveal: autolaunch: false scroll: true diff --git a/doc/source/tutorial/tutorial_IO.ipynb b/doc/source/tutorial/tutorial_IO.ipynb index d51b619fe..04f30770d 100644 --- a/doc/source/tutorial/tutorial_IO.ipynb +++ b/doc/source/tutorial/tutorial_IO.ipynb @@ -1,1066 +1,1066 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Load And Dump Arrays, Sessions, Axes And Groups\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "LArray provides methods and functions to load and dump LArray, Session, Axis Group objects to several formats such as Excel, CSV and HDF5. The HDF5 file format is designed to store and organize large amounts of data. It allows to read and write data much faster than when working with CSV and Excel files. \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "nbsphinx": "hidden" - }, - "outputs": [], - "source": [ - "# run this cell to avoid annoying warnings\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\", message=r'.*numpy.dtype size changed*')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# first of all, import the LArray library\n", - "from larray import *" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the version of LArray:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from larray import __version__\n", - "__version__" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Loading and Dumping Arrays\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Loading Arrays - Basic Usage (CSV, Excel, HDF5)\n", - "\n", - "To read an array from a CSV file, you must use the ``read_csv`` function:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "csv_dir = get_example_filepath('examples')\n", - "\n", - "# read the array pop from the file 'pop.csv'.\n", - "# The data of the array below is derived from a subset of the demo_pjan table from Eurostat\n", - "pop = read_csv(csv_dir + '/pop.csv')\n", - "pop" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To read an array from a sheet of an Excel file, you can use the ``read_excel`` function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filepath_excel = get_example_filepath('examples.xlsx')\n", - "\n", - "# read the array from the sheet 'births' of the Excel file 'examples.xlsx'\n", - "# The data of the array below is derived from a subset of the demo_fasec table from Eurostat\n", - "births = read_excel(filepath_excel, 'births')\n", - "births" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ``open_excel`` function in combination with the ``load`` method allows you to load several arrays from the same Workbook without opening and closing it several times:\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "# open the Excel file 'population.xlsx' and let it opened as long as you keep the indent.\n", - "# The Python keyword ``with`` ensures that the Excel file is properly closed even if an error occurs\n", - "with open_excel(filepath_excel) as wb:\n", - " # load the array 'pop' from the sheet 'pop' \n", - " pop = wb['pop'].load()\n", - " # load the array 'births' from the sheet 'births'\n", - " births = wb['births'].load()\n", - " # load the array 'deaths' from the sheet 'deaths'\n", - " deaths = wb['deaths'].load()\n", - "\n", - "# the Workbook is automatically closed when getting out the block defined by the with statement\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " **Warning:** `open_excel` requires to work on Windows and to have the library ``xlwings`` installed.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `HDF5` file format is specifically designed to store and organize large amounts of data. \n", - "Reading and writing data in this file format is much faster than with CSV or Excel. \n", - "An HDF5 file can contain multiple arrays, each array being associated with a key.\n", - "To read an array from an HDF5 file, you must use the ``read_hdf`` function and provide the key associated with the array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filepath_hdf = get_example_filepath('examples.h5')\n", - "\n", - "# read the array from the file 'examples.h5' associated with the key 'deaths'\n", - "# The data of the array below is derived from a subset of the demo_magec table from Eurostat\n", - "deaths = read_hdf(filepath_hdf, 'deaths')\n", - "deaths" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Dumping Arrays - Basic Usage (CSV, Excel, HDF5)\n", - "\n", - "To write an array in a CSV file, you must use the ``to_csv`` method:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# save the array pop in the file 'pop.csv'\n", - "pop.to_csv('pop.csv')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To write an array to a sheet of an Excel file, you can use the ``to_excel`` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# save the array pop in the sheet 'pop' of the Excel file 'population.xlsx' \n", - "pop.to_excel('population.xlsx', 'pop')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that ``to_excel`` create a new Excel file if it does not exist yet. \n", - "If the file already exists, a new sheet is added after the existing ones if that sheet does not already exists:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# add a new sheet 'births' to the file 'population.xlsx' and save the array births in it\n", - "births.to_excel('population.xlsx', 'births')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To reset an Excel file, you simply need to set the `overwrite_file` argument as True:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1. reset the file 'population.xlsx' (all sheets are removed)\n", - "# 2. create a sheet 'pop' and save the array pop in it\n", - "pop.to_excel('population.xlsx', 'pop', overwrite_file=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ``open_excel`` function in combination with the ``dump()`` method allows you to open a Workbook and to export several arrays at once. If the Excel file doesn't exist, the ``overwrite_file`` argument must be set to True.\n", - "\n", - "
\n", - " **Warning:** The ``save`` method must be called at the end of the block defined by the *with* statement to actually write data in the Excel file, otherwise you will end up with an empty file.\n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "# to create a new Excel file, argument overwrite_file must be set to True\n", - "with open_excel('population.xlsx', overwrite_file=True) as wb:\n", - " # add a new sheet 'pop' and dump the array pop in it \n", - " wb['pop'] = pop.dump()\n", - " # add a new sheet 'births' and dump the array births in it \n", - " wb['births'] = births.dump()\n", - " # add a new sheet 'deaths' and dump the array deaths in it \n", - " wb['deaths'] = deaths.dump()\n", - " # actually write data in the Workbook\n", - " wb.save()\n", - " \n", - "# the Workbook is automatically closed when getting out the block defined by the with statement\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To write an array in an HDF5 file, you must use the ``to_hdf`` function and provide the key that will be associated with the array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# save the array pop in the file 'population.h5' and associate it with the key 'pop'\n", - "pop.to_hdf('population.h5', 'pop')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Specifying Wide VS Narrow format (CSV, Excel)\n", - "\n", - "By default, all reading functions assume that arrays are stored in the ``wide`` format, meaning that their last axis is represented horizontally:\n", - "\n", - "| country \\\\ time | 2013 | 2014 | 2015 |\n", - "| --------------- | -------- | -------- | -------- |\n", - "| Belgium | 11137974 | 11180840 | 11237274 |\n", - "| France | 65600350 | 65942267 | 66456279 |\n", - "\n", - "By setting the ``wide`` argument to False, reading functions will assume instead that arrays are stored in the ``narrow`` format, i.e. one column per axis plus one value column:\n", - "\n", - "| country | time | value |\n", - "| ------- | ---- | -------- |\n", - "| Belgium | 2013 | 11137974 |\n", - "| Belgium | 2014 | 11180840 |\n", - "| Belgium | 2015 | 11237274 |\n", - "| France | 2013 | 65600350 |\n", - "| France | 2014 | 65942267 |\n", - "| France | 2015 | 66456279 |\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# set 'wide' argument to False to indicate that the array is stored in the 'narrow' format\n", - "pop_BE_FR = read_csv(csv_dir + '/pop_narrow_format.csv', wide=False)\n", - "pop_BE_FR" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# same for the read_excel function\n", - "pop_BE_FR = read_excel(filepath_excel, sheet='pop_narrow_format', wide=False)\n", - "pop_BE_FR" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, writing functions will set the name of the column containing the data to 'value'. You can choose the name of this column by using the ``value_name`` argument. For example, using ``value_name='population'`` you can export the previous array as:\n", - "\n", - "| country | time | population |\n", - "| ------- | ---- | ---------- |\n", - "| Belgium | 2013 | 11137974 |\n", - "| Belgium | 2014 | 11180840 |\n", - "| Belgium | 2015 | 11237274 |\n", - "| France | 2013 | 65600350 |\n", - "| France | 2014 | 65942267 |\n", - "| France | 2015 | 66456279 |\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# dump the array pop_BE_FR in a narrow format (one column per axis plus one value column).\n", - "# By default, the name of the column containing data is set to 'value'\n", - "pop_BE_FR.to_csv('pop_narrow_format.csv', wide=False)\n", - "\n", - "# same but replace 'value' by 'population'\n", - "pop_BE_FR.to_csv('pop_narrow_format.csv', wide=False, value_name='population')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# same for the to_excel method\n", - "pop_BE_FR.to_excel('population.xlsx', 'pop_narrow_format', wide=False, value_name='population')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Like with the ``to_excel`` method, it is possible to export arrays in a ``narrow`` format using ``open_excel``. \n", - "To do so, you must set the ``wide`` argument of the ``dump`` method to False:\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "with open_excel('population.xlsx') as wb:\n", - " # dump the array pop_BE_FR in a narrow format: \n", - " # one column per axis plus one value column.\n", - " # Argument value_name can be used to change the name of the \n", - " # column containing the data (default name is 'value')\n", - " wb['pop_narrow_format'] = pop_BE_FR.dump(wide=False, value_name='population')\n", - " # don't forget to call save()\n", - " wb.save()\n", - "\n", - "# in the sheet 'pop_narrow_format', data is written as:\n", - "# | country | time | value |\n", - "# | ------- | ---- | -------- |\n", - "# | Belgium | 2013 | 11137974 |\n", - "# | Belgium | 2014 | 11180840 |\n", - "# | Belgium | 2015 | 11237274 |\n", - "# | France | 2013 | 65600350 |\n", - "# | France | 2014 | 65942267 |\n", - "# | France | 2015 | 66456279 |\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Specifying Position in Sheet (Excel)\n", - "\n", - "If you want to read an array from an Excel sheet which does not start at cell `A1` (when there is more than one array stored in the same sheet for example), you will need to use the ``range`` argument. \n", - "\n", - "
\n", - " **Warning:** Note that the ``range`` argument is only available if you have the library ``xlwings`` installed (Windows).\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "# the 'range' argument must be used to load data not starting at cell A1.\n", - "# This is useful when there is several arrays stored in the same sheet\n", - "births = read_excel(filepath_excel, sheet='pop_births_deaths', range='A9:E15')\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using ``open_excel``, ranges are passed in brackets:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "with open_excel(filepath_excel) as wb:\n", - " # store sheet 'pop_births_deaths' in a temporary variable sh\n", - " sh = wb['pop_births_deaths']\n", - " # load the array pop from range A1:E7\n", - " pop = sh['A1:E7'].load()\n", - " # load the array births from range A9:E15\n", - " births = sh['A9:E15'].load()\n", - " # load the array deaths from range A17:E23\n", - " deaths = sh['A17:E23'].load()\n", - "\n", - "# the Workbook is automatically closed when getting out the block defined by the with statement\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When exporting arrays to Excel files, data is written starting at cell `A1` by default. Using the ``position`` argument of the ``to_excel`` method, it is possible to specify the top left cell of the dumped data. This can be useful when you want to export several arrays in the same sheet for example\n", - "\n", - "
\n", - " **Warning:** Note that the ``position`` argument is only available if you have the library ``xlwings`` installed (Windows).\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "filename = 'population.xlsx'\n", - "sheetname = 'pop_births_deaths'\n", - "\n", - "# save the arrays pop, births and deaths in the same sheet 'pop_births_and_deaths'.\n", - "# The 'position' argument is used to shift the location of the second and third arrays to be dumped\n", - "pop.to_excel(filename, sheetname)\n", - "births.to_excel(filename, sheetname, position='A9')\n", - "deaths.to_excel(filename, sheetname, position='A17')\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using ``open_excel``, the position is passed in brackets (this allows you to also add extra informations): \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "with open_excel('population.xlsx') as wb:\n", - " # add a new sheet 'pop_births_deaths' and write 'population' in the first cell\n", - " # note: you can use wb['new_sheet_name'] = '' to create an empty sheet\n", - " wb['pop_births_deaths'] = 'population'\n", - " # store sheet 'pop_births_deaths' in a temporary variable sh\n", - " sh = wb['pop_births_deaths']\n", - " # dump the array pop in sheet 'pop_births_deaths' starting at cell A2\n", - " sh['A2'] = pop.dump()\n", - " # add 'births' in cell A10\n", - " sh['A10'] = 'births'\n", - " # dump the array births in sheet 'pop_births_deaths' starting at cell A11 \n", - " sh['A11'] = births.dump()\n", - " # add 'deaths' in cell A19\n", - " sh['A19'] = 'deaths'\n", - " # dump the array deaths in sheet 'pop_births_deaths' starting at cell A20\n", - " sh['A20'] = deaths.dump()\n", - " # don't forget to call save()\n", - " wb.save()\n", - " \n", - "# the Workbook is automatically closed when getting out the block defined by the with statement\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exporting data without headers (Excel)\n", - "\n", - "For some reasons, you may want to export only the data of an array without axes. For example, you may want to insert a new column containing extra information. As an exercise, let us consider we want to add the capital city for each country present in the array containing the total population by country:\n", - "\n", - "| country | capital city | 2013 | 2014 | 2015 |\n", - "| ------- | ------------ | -------- | -------- | -------- |\n", - "| Belgium | Brussels | 11137974 | 11180840 | 11237274 |\n", - "| France | Paris | 65600350 | 65942267 | 66456279 |\n", - "| Germany | Berlin | 80523746 | 80767463 | 81197537 |\n", - "\n", - "Assuming you have prepared an excel sheet as below: \n", - "\n", - "| country | capital city | 2013 | 2014 | 2015 |\n", - "| ------- | ------------ | -------- | -------- | -------- |\n", - "| Belgium | Brussels | | | |\n", - "| France | Paris | | | |\n", - "| Germany | Berlin | | | ||\n", - "\n", - "you can then dump the data at right place by setting the ``header`` argument of ``to_excel`` to False and specifying the position of the data in sheet:\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "pop_by_country = pop.sum('gender')\n", - "\n", - "# export only the data of the array pop_by_country starting at cell C2\n", - "pop_by_country.to_excel('population.xlsx', 'pop_by_country', header=False, position='C2')\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using ``open_excel``, you can easily prepare the sheet and then export only data at the right place by either setting the ``header`` argument of the ``dump`` method to False or avoiding to call ``dump``:\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "with open_excel('population.xlsx') as wb:\n", - " # create new empty sheet 'pop_by_country'\n", - " wb['pop_by_country'] = ''\n", - " # store sheet 'pop_by_country' in a temporary variable sh\n", - " sh = wb['pop_by_country']\n", - " # write extra information (description)\n", - " sh['A1'] = 'Population at 1st January by country'\n", - " # export column names\n", - " sh['A2'] = ['country', 'capital city']\n", - " sh['C2'] = pop_by_country.time.labels\n", - " # export countries as first column\n", - " sh['A3'].options(transpose=True).value = pop_by_country.country.labels\n", - " # export capital cities as second column\n", - " sh['B3'].options(transpose=True).value = ['Brussels', 'Paris', 'Berlin']\n", - " # export only data of pop_by_country\n", - " sh['C3'] = pop_by_country.dump(header=False)\n", - " # or equivalently\n", - " sh['C3'] = pop_by_country\n", - " # don't forget to call save()\n", - " wb.save()\n", - " \n", - "# the Workbook is automatically closed when getting out the block defined by the with statement\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Specifying the Number of Axes at Reading (CSV, Excel)\n", - "\n", - "By default, ``read_csv`` and ``read_excel`` will search the position of the first cell containing the special character ``\\`` in the header line in order to determine the number of axes of the array to read. The special character ``\\`` is used to separate the name of the two last axes. If there is no special character ``\\``, ``read_csv`` and ``read_excel`` will consider that the array to read has only one dimension. For an array stored as:\n", - "\n", - "| country | gender \\\\ time | 2013 | 2014 | 2015 |\n", - "| ------- | -------------- | -------- | -------- | -------- |\n", - "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", - "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", - "| France | Male | 31772665 | 31936596 | 32175328 |\n", - "| France | Female | 33827685 | 34005671 | 34280951 |\n", - "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", - "| Germany | Female | 41142770 | 41210540 | 41362080 |\n", - "\n", - "``read_csv`` and ``read_excel`` will find the special character ``\\`` in the second cell meaning it expects three axes (country, gender and time). \n", - "\n", - "Sometimes, you need to read an array for which the name of the last axis is implicit: \n", - "\n", - "| country | gender | 2013 | 2014 | 2015 |\n", - "| ------- | ------ | -------- | -------- | -------- |\n", - "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", - "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", - "| France | Male | 31772665 | 31936596 | 32175328 |\n", - "| France | Female | 33827685 | 34005671 | 34280951 |\n", - "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", - "| Germany | Female | 41142770 | 41210540 | 41362080 |\n", - "\n", - "For such case, you will have to inform ``read_csv`` and ``read_excel`` of the number of axes of the output array by setting the ``nb_axes`` argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the 3 x 2 x 3 array stored in the file 'pop_missing_axis_name.csv' wihout using 'nb_axes' argument.\n", - "pop = read_csv(csv_dir + '/pop_missing_axis_name.csv')\n", - "# shape and data type of the output array are not what we expected\n", - "pop.info" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# by setting the 'nb_axes' argument, you can indicate to read_csv the number of axes of the output array\n", - "pop = read_csv(csv_dir + '/pop_missing_axis_name.csv', nb_axes=3)\n", - "\n", - "# give a name to the last axis\n", - "pop = pop.rename(-1, 'time')\n", - "\n", - "# shape and data type of the output array are what we expected\n", - "pop.info" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# same for the read_excel function\n", - "pop = read_excel(filepath_excel, sheet='pop_missing_axis_name', nb_axes=3)\n", - "pop = pop.rename(-1, 'time')\n", - "pop.info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### NaNs and Missing Data Handling at Reading (CSV, Excel)\n", - "\n", - "Sometimes, there is no data available for some label combinations. In the example below, the rows corresponding to `France - Male` and `Germany - Female` are missing:\n", - "\n", - "| country | gender \\\\ time | 2013 | 2014 | 2015 |\n", - "| ------- | -------------- | -------- | -------- | -------- |\n", - "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", - "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", - "| France | Female | 33827685 | 34005671 | 34280951 |\n", - "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", - "\n", - "By default, ``read_csv`` and ``read_excel`` will fill cells associated with missing label combinations with nans. \n", - "Be aware that, in that case, an int array will be converted to a float array." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# by default, cells associated will missing label combinations are filled with nans.\n", - "# In that case, the output array is converted to a float array\n", - "read_csv(csv_dir + '/pop_missing_values.csv')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, it is possible to choose which value to use to fill missing cells using the ``fill_value`` argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read_csv(csv_dir + '/pop_missing_values.csv', fill_value=0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# same for the read_excel function\n", - "read_excel(filepath_excel, sheet='pop_missing_values', fill_value=0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Sorting Axes at Reading (CSV, Excel, HDF5)\n", - "\n", - "The ``sort_rows`` and ``sort_columns`` arguments of the reading functions allows you to sort rows and columns alphabetically:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# sort labels at reading --> Male and Female labels are inverted\n", - "read_csv(csv_dir + '/pop.csv', sort_rows=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read_excel(filepath_excel, sheet='births', sort_rows=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read_hdf(filepath_hdf, key='deaths', sort_rows=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Metadata (HDF5)\n", - "\n", - "Since the version 0.29 of LArray, it is possible to add metadata to arrays:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pop.meta.title = 'Population at 1st January'\n", - "pop.meta.origin = 'Table demo_jpan from Eurostat'\n", - "\n", - "pop.info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These metadata are automatically saved and loaded when working with the HDF5 file format: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pop.to_hdf('population.h5', 'pop')\n", - "\n", - "new_pop = read_hdf('population.h5', 'pop')\n", - "new_pop.info" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " **Warning:** Currently, metadata associated with arrays cannot be saved and loaded when working with CSV and Excel files.\n", - " This restriction does not apply however to metadata associated with sessions.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Loading and Dumping Sessions\n", - "\n", - "One of the main advantages of grouping arrays, axes and groups in session objects is that you can load and save all of them in one shot. Like arrays, it is possible to associate metadata to a session. These can be saved and loaded in all file formats. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Loading Sessions (CSV, Excel, HDF5)\n", - "\n", - "To load the items of a session, you have two options:\n", - "\n", - "1) Instantiate a new session and pass the path to the Excel/HDF5 file or to the directory containing CSV files to the Session constructor:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a new Session object and load all arrays, axes, groups and metadata \n", - "# from all CSV files located in the passed directory\n", - "csv_dir = get_example_filepath('population_session')\n", - "session = Session(csv_dir)\n", - "\n", - "# create a new Session object and load all arrays, axes, groups and metadata\n", - "# stored in the passed Excel file\n", - "filepath_excel = get_example_filepath('population_session.xlsx')\n", - "session = Session(filepath_excel)\n", - "\n", - "# create a new Session object and load all arrays, axes, groups and metadata\n", - "# stored in the passed HDF5 file\n", - "filepath_hdf = get_example_filepath('population_session.h5')\n", - "session = Session(filepath_hdf)\n", - "\n", - "print(session.summary())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2) Call the ``load`` method on an existing session and pass the path to the Excel/HDF5 file or to the directory containing CSV files as first argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a session containing 3 axes, 2 groups and one array 'pop'\n", - "filepath = get_example_filepath('pop_only.xlsx')\n", - "session = Session(filepath)\n", - "\n", - "print(session.summary())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# call the load method on the previous session and add the 'births' and 'deaths' arrays to it\n", - "filepath = get_example_filepath('births_and_deaths.xlsx')\n", - "session.load(filepath)\n", - "\n", - "print(session.summary())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ``load`` method offers some options:\n", - "\n", - "1) Using the ``names`` argument, you can specify which items to load:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "session = Session()\n", - "\n", - "# use the names argument to only load births and deaths arrays\n", - "session.load(filepath_hdf, names=['births', 'deaths'])\n", - "\n", - "print(session.summary())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2) Setting the ``display`` argument to True, the ``load`` method will print a message each time a new item is loaded: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "session = Session()\n", - "\n", - "# with display=True, the load method will print a message\n", - "# each time a new item is loaded\n", - "session.load(filepath_hdf, display=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Dumping Sessions (CSV, Excel, HDF5)\n", - "\n", - "To save a session, you need to call the ``save`` method. The first argument is the path to a Excel/HDF5 file or to a directory if items are saved to CSV files:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# save items of a session in CSV files.\n", - "# Here, the save method will create a 'population' directory in which CSV files will be written \n", - "session.save('population')\n", - "\n", - "# save session to an HDF5 file\n", - "session.save('population.h5')\n", - "\n", - "# save session to an Excel file\n", - "session.save('population.xlsx')\n", - "\n", - "# load session saved in 'population.h5' to see its content\n", - "Session('population.h5')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " Note: Concerning the CSV and Excel formats: \n", - " \n", - " - all Axis objects are saved together in the same Excel sheet (CSV file) named `__axes__(.csv)` \n", - " - all Group objects are saved together in the same Excel sheet (CSV file) named `__groups__(.csv)` \n", - " - metadata is saved in one Excel sheet (CSV file) named `__metadata__(.csv)` \n", - " \n", - " These sheet (CSV file) names cannot be changed. \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The ``save`` method has several arguments:\n", - "\n", - "1) Using the ``names`` argument, you can specify which items to save:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# use the names argument to only save births and deaths arrays\n", - "session.save('population.h5', names=['births', 'deaths'])\n", - "\n", - "# load session saved in 'population.h5' to see its content\n", - "Session('population.h5')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2) By default, dumping a session to an Excel or HDF5 file will overwrite it. By setting the ``overwrite`` argument to False, you can choose to update the existing Excel or HDF5 file: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pop = read_csv('./population/pop.csv')\n", - "ses_pop = Session([('pop', pop)])\n", - "\n", - "# by setting overwrite to False, the destination file is updated instead of overwritten.\n", - "# The items already stored in the file but not present in the session are left intact. \n", - "# On the contrary, the items that exist in both the file and the session are completely overwritten.\n", - "ses_pop.save('population.h5', overwrite=False)\n", - "\n", - "# load session saved in 'population.h5' to see its content\n", - "Session('population.h5')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "3) Setting the ``display`` argument to True, the ``save`` method will print a message each time an item is dumped: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# with display=True, the save method will print a message\n", - "# each time an item is dumped\n", - "session.save('population.h5', display=True)" - ] - } - ], - "metadata": { - "celltoolbar": "Edit Metadata", - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - }, - "livereveal": { - "autolaunch": false, - "scroll": true - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Load And Dump Arrays, Sessions, Axes And Groups\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "LArray provides methods and functions to load and dump LArray, Session, Axis Group objects to several formats such as Excel, CSV and HDF5. The HDF5 file format is designed to store and organize large amounts of data. It allows to read and write data much faster than when working with CSV and Excel files. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "nbsphinx": "hidden" + }, + "outputs": [], + "source": [ + "# run this cell to avoid annoying warnings\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\", message=r'.*numpy.dtype size changed*')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# first of all, import the LArray library\n", + "from larray import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the version of LArray:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from larray import __version__\n", + "__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading and Dumping Arrays\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading Arrays - Basic Usage (CSV, Excel, HDF5)\n", + "\n", + "To read an array from a CSV file, you must use the ``read_csv`` function:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "csv_dir = get_example_filepath('examples')\n", + "\n", + "# read the array pop from the file 'pop.csv'.\n", + "# The data of the array below is derived from a subset of the demo_pjan table from Eurostat\n", + "pop = read_csv(csv_dir + '/pop.csv')\n", + "pop" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To read an array from a sheet of an Excel file, you can use the ``read_excel`` function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "filepath_excel = get_example_filepath('examples.xlsx')\n", + "\n", + "# read the array from the sheet 'births' of the Excel file 'examples.xlsx'\n", + "# The data of the array below is derived from a subset of the demo_fasec table from Eurostat\n", + "births = read_excel(filepath_excel, 'births')\n", + "births" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``open_excel`` function in combination with the ``load`` method allows you to load several arrays from the same Workbook without opening and closing it several times:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# open the Excel file 'population.xlsx' and let it opened as long as you keep the indent.\n", + "# The Python keyword ``with`` ensures that the Excel file is properly closed even if an error occurs\n", + "with open_excel(filepath_excel) as wb:\n", + " # load the array 'pop' from the sheet 'pop' \n", + " pop = wb['pop'].load()\n", + " # load the array 'births' from the sheet 'births'\n", + " births = wb['births'].load()\n", + " # load the array 'deaths' from the sheet 'deaths'\n", + " deaths = wb['deaths'].load()\n", + "\n", + "# the Workbook is automatically closed when getting out the block defined by the with statement\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " **Warning:** `open_excel` requires to work on Windows and to have the library ``xlwings`` installed.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `HDF5` file format is specifically designed to store and organize large amounts of data. \n", + "Reading and writing data in this file format is much faster than with CSV or Excel. \n", + "An HDF5 file can contain multiple arrays, each array being associated with a key.\n", + "To read an array from an HDF5 file, you must use the ``read_hdf`` function and provide the key associated with the array:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "filepath_hdf = get_example_filepath('examples.h5')\n", + "\n", + "# read the array from the file 'examples.h5' associated with the key 'deaths'\n", + "# The data of the array below is derived from a subset of the demo_magec table from Eurostat\n", + "deaths = read_hdf(filepath_hdf, 'deaths')\n", + "deaths" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dumping Arrays - Basic Usage (CSV, Excel, HDF5)\n", + "\n", + "To write an array in a CSV file, you must use the ``to_csv`` method:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# save the array pop in the file 'pop.csv'\n", + "pop.to_csv('pop.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To write an array to a sheet of an Excel file, you can use the ``to_excel`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# save the array pop in the sheet 'pop' of the Excel file 'population.xlsx' \n", + "pop.to_excel('population.xlsx', 'pop')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that ``to_excel`` create a new Excel file if it does not exist yet. \n", + "If the file already exists, a new sheet is added after the existing ones if that sheet does not already exists:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# add a new sheet 'births' to the file 'population.xlsx' and save the array births in it\n", + "births.to_excel('population.xlsx', 'births')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To reset an Excel file, you simply need to set the `overwrite_file` argument as True:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 1. reset the file 'population.xlsx' (all sheets are removed)\n", + "# 2. create a sheet 'pop' and save the array pop in it\n", + "pop.to_excel('population.xlsx', 'pop', overwrite_file=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``open_excel`` function in combination with the ``dump()`` method allows you to open a Workbook and to export several arrays at once. If the Excel file doesn't exist, the ``overwrite_file`` argument must be set to True.\n", + "\n", + "
\n", + " **Warning:** The ``save`` method must be called at the end of the block defined by the *with* statement to actually write data in the Excel file, otherwise you will end up with an empty file.\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# to create a new Excel file, argument overwrite_file must be set to True\n", + "with open_excel('population.xlsx', overwrite_file=True) as wb:\n", + " # add a new sheet 'pop' and dump the array pop in it \n", + " wb['pop'] = pop.dump()\n", + " # add a new sheet 'births' and dump the array births in it \n", + " wb['births'] = births.dump()\n", + " # add a new sheet 'deaths' and dump the array deaths in it \n", + " wb['deaths'] = deaths.dump()\n", + " # actually write data in the Workbook\n", + " wb.save()\n", + " \n", + "# the Workbook is automatically closed when getting out the block defined by the with statement\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To write an array in an HDF5 file, you must use the ``to_hdf`` function and provide the key that will be associated with the array:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# save the array pop in the file 'population.h5' and associate it with the key 'pop'\n", + "pop.to_hdf('population.h5', 'pop')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Specifying Wide VS Narrow format (CSV, Excel)\n", + "\n", + "By default, all reading functions assume that arrays are stored in the ``wide`` format, meaning that their last axis is represented horizontally:\n", + "\n", + "| country \\\\ time | 2013 | 2014 | 2015 |\n", + "| --------------- | -------- | -------- | -------- |\n", + "| Belgium | 11137974 | 11180840 | 11237274 |\n", + "| France | 65600350 | 65942267 | 66456279 |\n", + "\n", + "By setting the ``wide`` argument to False, reading functions will assume instead that arrays are stored in the ``narrow`` format, i.e. one column per axis plus one value column:\n", + "\n", + "| country | time | value |\n", + "| ------- | ---- | -------- |\n", + "| Belgium | 2013 | 11137974 |\n", + "| Belgium | 2014 | 11180840 |\n", + "| Belgium | 2015 | 11237274 |\n", + "| France | 2013 | 65600350 |\n", + "| France | 2014 | 65942267 |\n", + "| France | 2015 | 66456279 |\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# set 'wide' argument to False to indicate that the array is stored in the 'narrow' format\n", + "pop_BE_FR = read_csv(csv_dir + '/pop_narrow_format.csv', wide=False)\n", + "pop_BE_FR" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# same for the read_excel function\n", + "pop_BE_FR = read_excel(filepath_excel, sheet='pop_narrow_format', wide=False)\n", + "pop_BE_FR" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, writing functions will set the name of the column containing the data to 'value'. You can choose the name of this column by using the ``value_name`` argument. For example, using ``value_name='population'`` you can export the previous array as:\n", + "\n", + "| country | time | population |\n", + "| ------- | ---- | ---------- |\n", + "| Belgium | 2013 | 11137974 |\n", + "| Belgium | 2014 | 11180840 |\n", + "| Belgium | 2015 | 11237274 |\n", + "| France | 2013 | 65600350 |\n", + "| France | 2014 | 65942267 |\n", + "| France | 2015 | 66456279 |\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# dump the array pop_BE_FR in a narrow format (one column per axis plus one value column).\n", + "# By default, the name of the column containing data is set to 'value'\n", + "pop_BE_FR.to_csv('pop_narrow_format.csv', wide=False)\n", + "\n", + "# same but replace 'value' by 'population'\n", + "pop_BE_FR.to_csv('pop_narrow_format.csv', wide=False, value_name='population')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# same for the to_excel method\n", + "pop_BE_FR.to_excel('population.xlsx', 'pop_narrow_format', wide=False, value_name='population')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Like with the ``to_excel`` method, it is possible to export arrays in a ``narrow`` format using ``open_excel``. \n", + "To do so, you must set the ``wide`` argument of the ``dump`` method to False:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "with open_excel('population.xlsx') as wb:\n", + " # dump the array pop_BE_FR in a narrow format: \n", + " # one column per axis plus one value column.\n", + " # Argument value_name can be used to change the name of the \n", + " # column containing the data (default name is 'value')\n", + " wb['pop_narrow_format'] = pop_BE_FR.dump(wide=False, value_name='population')\n", + " # don't forget to call save()\n", + " wb.save()\n", + "\n", + "# in the sheet 'pop_narrow_format', data is written as:\n", + "# | country | time | value |\n", + "# | ------- | ---- | -------- |\n", + "# | Belgium | 2013 | 11137974 |\n", + "# | Belgium | 2014 | 11180840 |\n", + "# | Belgium | 2015 | 11237274 |\n", + "# | France | 2013 | 65600350 |\n", + "# | France | 2014 | 65942267 |\n", + "# | France | 2015 | 66456279 |\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Specifying Position in Sheet (Excel)\n", + "\n", + "If you want to read an array from an Excel sheet which does not start at cell `A1` (when there is more than one array stored in the same sheet for example), you will need to use the ``range`` argument. \n", + "\n", + "
\n", + " **Warning:** Note that the ``range`` argument is only available if you have the library ``xlwings`` installed (Windows).\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# the 'range' argument must be used to load data not starting at cell A1.\n", + "# This is useful when there is several arrays stored in the same sheet\n", + "births = read_excel(filepath_excel, sheet='pop_births_deaths', range='A9:E15')\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using ``open_excel``, ranges are passed in brackets:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "with open_excel(filepath_excel) as wb:\n", + " # store sheet 'pop_births_deaths' in a temporary variable sh\n", + " sh = wb['pop_births_deaths']\n", + " # load the array pop from range A1:E7\n", + " pop = sh['A1:E7'].load()\n", + " # load the array births from range A9:E15\n", + " births = sh['A9:E15'].load()\n", + " # load the array deaths from range A17:E23\n", + " deaths = sh['A17:E23'].load()\n", + "\n", + "# the Workbook is automatically closed when getting out the block defined by the with statement\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When exporting arrays to Excel files, data is written starting at cell `A1` by default. Using the ``position`` argument of the ``to_excel`` method, it is possible to specify the top left cell of the dumped data. This can be useful when you want to export several arrays in the same sheet for example\n", + "\n", + "
\n", + " **Warning:** Note that the ``position`` argument is only available if you have the library ``xlwings`` installed (Windows).\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "filename = 'population.xlsx'\n", + "sheetname = 'pop_births_deaths'\n", + "\n", + "# save the arrays pop, births and deaths in the same sheet 'pop_births_and_deaths'.\n", + "# The 'position' argument is used to shift the location of the second and third arrays to be dumped\n", + "pop.to_excel(filename, sheetname)\n", + "births.to_excel(filename, sheetname, position='A9')\n", + "deaths.to_excel(filename, sheetname, position='A17')\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using ``open_excel``, the position is passed in brackets (this allows you to also add extra informations): \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "with open_excel('population.xlsx') as wb:\n", + " # add a new sheet 'pop_births_deaths' and write 'population' in the first cell\n", + " # note: you can use wb['new_sheet_name'] = '' to create an empty sheet\n", + " wb['pop_births_deaths'] = 'population'\n", + " # store sheet 'pop_births_deaths' in a temporary variable sh\n", + " sh = wb['pop_births_deaths']\n", + " # dump the array pop in sheet 'pop_births_deaths' starting at cell A2\n", + " sh['A2'] = pop.dump()\n", + " # add 'births' in cell A10\n", + " sh['A10'] = 'births'\n", + " # dump the array births in sheet 'pop_births_deaths' starting at cell A11 \n", + " sh['A11'] = births.dump()\n", + " # add 'deaths' in cell A19\n", + " sh['A19'] = 'deaths'\n", + " # dump the array deaths in sheet 'pop_births_deaths' starting at cell A20\n", + " sh['A20'] = deaths.dump()\n", + " # don't forget to call save()\n", + " wb.save()\n", + " \n", + "# the Workbook is automatically closed when getting out the block defined by the with statement\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exporting data without headers (Excel)\n", + "\n", + "For some reasons, you may want to export only the data of an array without axes. For example, you may want to insert a new column containing extra information. As an exercise, let us consider we want to add the capital city for each country present in the array containing the total population by country:\n", + "\n", + "| country | capital city | 2013 | 2014 | 2015 |\n", + "| ------- | ------------ | -------- | -------- | -------- |\n", + "| Belgium | Brussels | 11137974 | 11180840 | 11237274 |\n", + "| France | Paris | 65600350 | 65942267 | 66456279 |\n", + "| Germany | Berlin | 80523746 | 80767463 | 81197537 |\n", + "\n", + "Assuming you have prepared an excel sheet as below: \n", + "\n", + "| country | capital city | 2013 | 2014 | 2015 |\n", + "| ------- | ------------ | -------- | -------- | -------- |\n", + "| Belgium | Brussels | | | |\n", + "| France | Paris | | | |\n", + "| Germany | Berlin | | | ||\n", + "\n", + "you can then dump the data at right place by setting the ``header`` argument of ``to_excel`` to False and specifying the position of the data in sheet:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "pop_by_country = pop.sum('gender')\n", + "\n", + "# export only the data of the array pop_by_country starting at cell C2\n", + "pop_by_country.to_excel('population.xlsx', 'pop_by_country', header=False, position='C2')\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using ``open_excel``, you can easily prepare the sheet and then export only data at the right place by either setting the ``header`` argument of the ``dump`` method to False or avoiding to call ``dump``:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "with open_excel('population.xlsx') as wb:\n", + " # create new empty sheet 'pop_by_country'\n", + " wb['pop_by_country'] = ''\n", + " # store sheet 'pop_by_country' in a temporary variable sh\n", + " sh = wb['pop_by_country']\n", + " # write extra information (description)\n", + " sh['A1'] = 'Population at 1st January by country'\n", + " # export column names\n", + " sh['A2'] = ['country', 'capital city']\n", + " sh['C2'] = pop_by_country.time.labels\n", + " # export countries as first column\n", + " sh['A3'].options(transpose=True).value = pop_by_country.country.labels\n", + " # export capital cities as second column\n", + " sh['B3'].options(transpose=True).value = ['Brussels', 'Paris', 'Berlin']\n", + " # export only data of pop_by_country\n", + " sh['C3'] = pop_by_country.dump(header=False)\n", + " # or equivalently\n", + " sh['C3'] = pop_by_country\n", + " # don't forget to call save()\n", + " wb.save()\n", + " \n", + "# the Workbook is automatically closed when getting out the block defined by the with statement\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Specifying the Number of Axes at Reading (CSV, Excel)\n", + "\n", + "By default, ``read_csv`` and ``read_excel`` will search the position of the first cell containing the special character ``\\`` in the header line in order to determine the number of axes of the array to read. The special character ``\\`` is used to separate the name of the two last axes. If there is no special character ``\\``, ``read_csv`` and ``read_excel`` will consider that the array to read has only one dimension. For an array stored as:\n", + "\n", + "| country | gender \\\\ time | 2013 | 2014 | 2015 |\n", + "| ------- | -------------- | -------- | -------- | -------- |\n", + "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", + "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", + "| France | Male | 31772665 | 31936596 | 32175328 |\n", + "| France | Female | 33827685 | 34005671 | 34280951 |\n", + "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", + "| Germany | Female | 41142770 | 41210540 | 41362080 |\n", + "\n", + "``read_csv`` and ``read_excel`` will find the special character ``\\`` in the second cell meaning it expects three axes (country, gender and time). \n", + "\n", + "Sometimes, you need to read an array for which the name of the last axis is implicit: \n", + "\n", + "| country | gender | 2013 | 2014 | 2015 |\n", + "| ------- | ------ | -------- | -------- | -------- |\n", + "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", + "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", + "| France | Male | 31772665 | 31936596 | 32175328 |\n", + "| France | Female | 33827685 | 34005671 | 34280951 |\n", + "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", + "| Germany | Female | 41142770 | 41210540 | 41362080 |\n", + "\n", + "For such case, you will have to inform ``read_csv`` and ``read_excel`` of the number of axes of the output array by setting the ``nb_axes`` argument:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# read the 3 x 2 x 3 array stored in the file 'pop_missing_axis_name.csv' wihout using 'nb_axes' argument.\n", + "pop = read_csv(csv_dir + '/pop_missing_axis_name.csv')\n", + "# shape and data type of the output array are not what we expected\n", + "pop.info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# by setting the 'nb_axes' argument, you can indicate to read_csv the number of axes of the output array\n", + "pop = read_csv(csv_dir + '/pop_missing_axis_name.csv', nb_axes=3)\n", + "\n", + "# give a name to the last axis\n", + "pop = pop.rename(-1, 'time')\n", + "\n", + "# shape and data type of the output array are what we expected\n", + "pop.info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# same for the read_excel function\n", + "pop = read_excel(filepath_excel, sheet='pop_missing_axis_name', nb_axes=3)\n", + "pop = pop.rename(-1, 'time')\n", + "pop.info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NaNs and Missing Data Handling at Reading (CSV, Excel)\n", + "\n", + "Sometimes, there is no data available for some label combinations. In the example below, the rows corresponding to `France - Male` and `Germany - Female` are missing:\n", + "\n", + "| country | gender \\\\ time | 2013 | 2014 | 2015 |\n", + "| ------- | -------------- | -------- | -------- | -------- |\n", + "| Belgium | Male | 5472856 | 5493792 | 5524068 |\n", + "| Belgium | Female | 5665118 | 5687048 | 5713206 |\n", + "| France | Female | 33827685 | 34005671 | 34280951 |\n", + "| Germany | Male | 39380976 | 39556923 | 39835457 |\n", + "\n", + "By default, ``read_csv`` and ``read_excel`` will fill cells associated with missing label combinations with nans. \n", + "Be aware that, in that case, an int array will be converted to a float array." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# by default, cells associated will missing label combinations are filled with nans.\n", + "# In that case, the output array is converted to a float array\n", + "read_csv(csv_dir + '/pop_missing_values.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, it is possible to choose which value to use to fill missing cells using the ``fill_value`` argument:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "read_csv(csv_dir + '/pop_missing_values.csv', fill_value=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# same for the read_excel function\n", + "read_excel(filepath_excel, sheet='pop_missing_values', fill_value=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sorting Axes at Reading (CSV, Excel, HDF5)\n", + "\n", + "The ``sort_rows`` and ``sort_columns`` arguments of the reading functions allows you to sort rows and columns alphabetically:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# sort labels at reading --> Male and Female labels are inverted\n", + "read_csv(csv_dir + '/pop.csv', sort_rows=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "read_excel(filepath_excel, sheet='births', sort_rows=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "read_hdf(filepath_hdf, key='deaths').sort_axes()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metadata (HDF5)\n", + "\n", + "Since the version 0.29 of LArray, it is possible to add metadata to arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop.meta.title = 'Population at 1st January'\n", + "pop.meta.origin = 'Table demo_jpan from Eurostat'\n", + "\n", + "pop.info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These metadata are automatically saved and loaded when working with the HDF5 file format: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop.to_hdf('population.h5', 'pop')\n", + "\n", + "new_pop = read_hdf('population.h5', 'pop')\n", + "new_pop.info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " **Warning:** Currently, metadata associated with arrays cannot be saved and loaded when working with CSV and Excel files.\n", + " This restriction does not apply however to metadata associated with sessions.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading and Dumping Sessions\n", + "\n", + "One of the main advantages of grouping arrays, axes and groups in session objects is that you can load and save all of them in one shot. Like arrays, it is possible to associate metadata to a session. These can be saved and loaded in all file formats. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading Sessions (CSV, Excel, HDF5)\n", + "\n", + "To load the items of a session, you have two options:\n", + "\n", + "1) Instantiate a new session and pass the path to the Excel/HDF5 file or to the directory containing CSV files to the Session constructor:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create a new Session object and load all arrays, axes, groups and metadata \n", + "# from all CSV files located in the passed directory\n", + "csv_dir = get_example_filepath('population_session')\n", + "session = Session(csv_dir)\n", + "\n", + "# create a new Session object and load all arrays, axes, groups and metadata\n", + "# stored in the passed Excel file\n", + "filepath_excel = get_example_filepath('population_session.xlsx')\n", + "session = Session(filepath_excel)\n", + "\n", + "# create a new Session object and load all arrays, axes, groups and metadata\n", + "# stored in the passed HDF5 file\n", + "filepath_hdf = get_example_filepath('population_session.h5')\n", + "session = Session(filepath_hdf)\n", + "\n", + "print(session.summary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2) Call the ``load`` method on an existing session and pass the path to the Excel/HDF5 file or to the directory containing CSV files as first argument:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create a session containing 3 axes, 2 groups and one array 'pop'\n", + "filepath = get_example_filepath('pop_only.xlsx')\n", + "session = Session(filepath)\n", + "\n", + "print(session.summary())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# call the load method on the previous session and add the 'births' and 'deaths' arrays to it\n", + "filepath = get_example_filepath('births_and_deaths.xlsx')\n", + "session.load(filepath)\n", + "\n", + "print(session.summary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``load`` method offers some options:\n", + "\n", + "1) Using the ``names`` argument, you can specify which items to load:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "session = Session()\n", + "\n", + "# use the names argument to only load births and deaths arrays\n", + "session.load(filepath_hdf, names=['births', 'deaths'])\n", + "\n", + "print(session.summary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2) Setting the ``display`` argument to True, the ``load`` method will print a message each time a new item is loaded: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "session = Session()\n", + "\n", + "# with display=True, the load method will print a message\n", + "# each time a new item is loaded\n", + "session.load(filepath_hdf, display=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dumping Sessions (CSV, Excel, HDF5)\n", + "\n", + "To save a session, you need to call the ``save`` method. The first argument is the path to a Excel/HDF5 file or to a directory if items are saved to CSV files:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# save items of a session in CSV files.\n", + "# Here, the save method will create a 'population' directory in which CSV files will be written \n", + "session.save('population')\n", + "\n", + "# save session to an HDF5 file\n", + "session.save('population.h5')\n", + "\n", + "# save session to an Excel file\n", + "session.save('population.xlsx')\n", + "\n", + "# load session saved in 'population.h5' to see its content\n", + "Session('population.h5')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " Note: Concerning the CSV and Excel formats: \n", + " \n", + " - all Axis objects are saved together in the same Excel sheet (CSV file) named `__axes__(.csv)` \n", + " - all Group objects are saved together in the same Excel sheet (CSV file) named `__groups__(.csv)` \n", + " - metadata is saved in one Excel sheet (CSV file) named `__metadata__(.csv)` \n", + " \n", + " These sheet (CSV file) names cannot be changed. \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``save`` method has several arguments:\n", + "\n", + "1) Using the ``names`` argument, you can specify which items to save:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# use the names argument to only save births and deaths arrays\n", + "session.save('population.h5', names=['births', 'deaths'])\n", + "\n", + "# load session saved in 'population.h5' to see its content\n", + "Session('population.h5')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2) By default, dumping a session to an Excel or HDF5 file will overwrite it. By setting the ``overwrite`` argument to False, you can choose to update the existing Excel or HDF5 file: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop = read_csv('./population/pop.csv')\n", + "ses_pop = Session([('pop', pop)])\n", + "\n", + "# by setting overwrite to False, the destination file is updated instead of overwritten.\n", + "# The items already stored in the file but not present in the session are left intact. \n", + "# On the contrary, the items that exist in both the file and the session are completely overwritten.\n", + "ses_pop.save('population.h5', overwrite=False)\n", + "\n", + "# load session saved in 'population.h5' to see its content\n", + "Session('population.h5')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3) Setting the ``display`` argument to True, the ``save`` method will print a message each time an item is dumped: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# with display=True, the save method will print a message\n", + "# each time an item is dumped\n", + "session.save('population.h5', display=True)" + ] + } + ], + "metadata": { + "celltoolbar": "Edit Metadata", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + }, + "livereveal": { + "autolaunch": false, + "scroll": true + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From b58d8a9af2122fd3c8e70009fa6a909193c08b55 Mon Sep 17 00:00:00 2001 From: Alix Damman Date: Fri, 27 Sep 2019 10:43:39 +0200 Subject: [PATCH 2/3] modified the LArray.percent() method because it may lead to negative values if the array contains integer values close to the highest integer value available --- larray/core/array.py | 3 +-- larray/tests/test_array.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/larray/core/array.py b/larray/core/array.py index 17612fa91..39a4ac7c8 100644 --- a/larray/core/array.py +++ b/larray/core/array.py @@ -3789,8 +3789,7 @@ def percent(self, *axes): BE 40.0 60.0 FO 20.0 80.0 """ - # dividing by self.sum(*axes) * 0.01 would be faster in many cases but I suspect it loose more precision. - return self * 100 / self.sum(*axes) + return self * 100.0 / self.sum(*axes) # aggregate method decorator def _decorate_agg_method(npfunc, nanfunc=None, commutative=False, by_agg=False, extra_kwargs=[], diff --git a/larray/tests/test_array.py b/larray/tests/test_array.py index 0615f987e..10871e498 100644 --- a/larray/tests/test_array.py +++ b/larray/tests/test_array.py @@ -2484,15 +2484,15 @@ def test_percent(array): reg = array.sum(age, sex, regions) percent = reg.percent() - assert_array_equal(percent, reg * 100 / reg.sum(geo, lipro)) + assert_array_equal(percent, (reg * 100.0 / reg.sum(geo, lipro))) assert percent.shape == (3, 15) percent = reg.percent(geo) - assert_array_equal(percent, reg * 100 / reg.sum(geo)) + assert_array_equal(percent, (reg * 100.0 / reg.sum(geo))) assert percent.shape == (3, 15) percent = reg.percent(geo, lipro) - assert_array_equal(percent, reg * 100 / reg.sum(geo, lipro)) + assert_array_equal(percent, (reg * 100.0 / reg.sum(geo, lipro))) assert percent.shape == (3, 15) assert round(abs(percent.sum() - 100.0), 7) == 0 From d4ce47367ad509901698635eb3290cd09cdabe8c Mon Sep 17 00:00:00 2001 From: Alix Damman Date: Tue, 4 Jun 2019 10:58:43 +0200 Subject: [PATCH 3/3] updated tutorial: - added generate_data.py module to generate the example and test data - renamed 'population_session' dataset as 'demography_eurostat' - included values for years 2016 and 2017 for all arrays of 'demography_eurostat' - fix #785 - added the 'Pythonic VS String Syntax' section - updated all existing sections to include changes up to the 0.31 release version --- doc/source/tutorial.rst | 1 + doc/source/tutorial/tutorial_IO.ipyml | 6 +- doc/source/tutorial/tutorial_IO.ipynb | 6 +- ...torial_arithmetic_op_and_aggregation.ipyml | 319 ++++++----- ...torial_arithmetic_op_and_aggregation.ipynb | 331 +++++++---- doc/source/tutorial/tutorial_indexing.ipyml | 381 ++++++------- doc/source/tutorial/tutorial_indexing.ipynb | 360 ++++++------ .../tutorial/tutorial_miscellaneous.ipyml | 182 +++--- .../tutorial/tutorial_miscellaneous.ipynb | 202 ++++--- doc/source/tutorial/tutorial_plotting.ipyml | 31 +- doc/source/tutorial/tutorial_plotting.ipynb | 20 +- .../tutorial_presenting_larray_objects.ipyml | 6 +- .../tutorial_presenting_larray_objects.ipynb | 6 +- doc/source/tutorial/tutorial_sessions.ipyml | 168 ++++-- doc/source/tutorial/tutorial_sessions.ipynb | 219 +++++-- .../tutorial/tutorial_string_syntax.ipyml | 162 ++++++ .../tutorial/tutorial_string_syntax.ipynb | 276 +++++++++ .../tutorial/tutorial_transforming.ipyml | 408 +++++++++---- .../tutorial/tutorial_transforming.ipynb | 535 +++++++++++++++--- larray/example.py | 44 +- larray/inout/csv.py | 24 +- larray/inout/excel.py | 20 +- larray/inout/hdf.py | 4 +- larray/inout/xw_reporting.py | 12 +- larray/tests/data/births_and_deaths.xlsx | Bin 9776 -> 9788 bytes larray/tests/data/demography_eurostat.h5 | Bin 0 -> 53216 bytes larray/tests/data/demography_eurostat.xlsx | Bin 0 -> 15834 bytes .../data/demography_eurostat/__axes__.csv | 6 + .../__groups__.csv | 3 +- .../data/demography_eurostat/__metadata__.csv | 3 + .../tests/data/demography_eurostat/births.csv | 7 + .../tests/data/demography_eurostat/deaths.csv | 7 + .../data/demography_eurostat/immigration.csv | 19 + larray/tests/data/demography_eurostat/pop.csv | 7 + .../data/demography_eurostat/pop_benelux.csv | 7 + larray/tests/data/examples.h5 | Bin 23256 -> 32216 bytes larray/tests/data/examples.xlsx | Bin 14722 -> 16097 bytes larray/tests/data/examples/immigration.csv | 19 + larray/tests/data/examples/pop.csv | 4 +- .../data/examples/pop_missing_axis_name.csv | 4 +- .../data/examples/pop_missing_values.csv | 2 +- .../tests/data/examples/pop_narrow_format.csv | 4 +- larray/tests/data/pop_only.xlsx | Bin 10324 -> 9636 bytes larray/tests/data/population_session.h5 | Bin 3190240 -> 0 bytes larray/tests/data/population_session.xlsx | Bin 12122 -> 0 bytes .../data/population_session/__axes__.csv | 4 - .../tests/data/population_session/births.csv | 7 - .../tests/data/population_session/deaths.csv | 7 - larray/tests/data/population_session/pop.csv | 7 - larray/tests/data/test.xlsx | Bin 16173 -> 14387 bytes larray/tests/data/test2d_classic.csv | 2 +- larray/tests/data/test2d_classic_narrow.csv | 10 + larray/tests/data/test_narrow.xlsx | Bin 13676 -> 15660 bytes larray/tests/data/testint_labels_narrow.csv | 28 + larray/tests/data/testunsorted.csv | 7 + larray/tests/generate_data.py | 194 +++++++ larray/tests/test_excel.py | 2 +- 57 files changed, 2887 insertions(+), 1196 deletions(-) create mode 100644 doc/source/tutorial/tutorial_string_syntax.ipyml create mode 100644 doc/source/tutorial/tutorial_string_syntax.ipynb create mode 100644 larray/tests/data/demography_eurostat.h5 create mode 100644 larray/tests/data/demography_eurostat.xlsx create mode 100644 larray/tests/data/demography_eurostat/__axes__.csv rename larray/tests/data/{population_session => demography_eurostat}/__groups__.csv (71%) create mode 100644 larray/tests/data/demography_eurostat/__metadata__.csv create mode 100644 larray/tests/data/demography_eurostat/births.csv create mode 100644 larray/tests/data/demography_eurostat/deaths.csv create mode 100644 larray/tests/data/demography_eurostat/immigration.csv create mode 100644 larray/tests/data/demography_eurostat/pop.csv create mode 100644 larray/tests/data/demography_eurostat/pop_benelux.csv create mode 100644 larray/tests/data/examples/immigration.csv delete mode 100644 larray/tests/data/population_session.h5 delete mode 100644 larray/tests/data/population_session.xlsx delete mode 100644 larray/tests/data/population_session/__axes__.csv delete mode 100644 larray/tests/data/population_session/births.csv delete mode 100644 larray/tests/data/population_session/deaths.csv delete mode 100644 larray/tests/data/population_session/pop.csv create mode 100644 larray/tests/data/test2d_classic_narrow.csv create mode 100644 larray/tests/data/testint_labels_narrow.csv create mode 100644 larray/tests/data/testunsorted.csv create mode 100644 larray/tests/generate_data.py diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index ccefb6a15..18e680ca3 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -18,6 +18,7 @@ It is mainly dedicated to help new users to familiarize with it and others to re ./tutorial/tutorial_transforming.ipynb ./tutorial/tutorial_indexing.ipynb ./tutorial/tutorial_arithmetic_op_and_aggregation.ipynb + ./tutorial/tutorial_string_syntax.ipynb ./tutorial/tutorial_plotting.ipynb ./tutorial/tutorial_miscellaneous.ipynb ./tutorial/tutorial_sessions.ipynb diff --git a/doc/source/tutorial/tutorial_IO.ipyml b/doc/source/tutorial/tutorial_IO.ipyml index c6a7ecca2..23296c9ac 100644 --- a/doc/source/tutorial/tutorial_IO.ipyml +++ b/doc/source/tutorial/tutorial_IO.ipyml @@ -574,17 +574,17 @@ cells: - code: | # create a new Session object and load all arrays, axes, groups and metadata # from all CSV files located in the passed directory - csv_dir = get_example_filepath('population_session') + csv_dir = get_example_filepath('demography_eurostat') session = Session(csv_dir) # create a new Session object and load all arrays, axes, groups and metadata # stored in the passed Excel file - filepath_excel = get_example_filepath('population_session.xlsx') + filepath_excel = get_example_filepath('demography_eurostat.xlsx') session = Session(filepath_excel) # create a new Session object and load all arrays, axes, groups and metadata # stored in the passed HDF5 file - filepath_hdf = get_example_filepath('population_session.h5') + filepath_hdf = get_example_filepath('demography_eurostat.h5') session = Session(filepath_hdf) print(session.summary()) diff --git a/doc/source/tutorial/tutorial_IO.ipynb b/doc/source/tutorial/tutorial_IO.ipynb index 04f30770d..6d9225e23 100644 --- a/doc/source/tutorial/tutorial_IO.ipynb +++ b/doc/source/tutorial/tutorial_IO.ipynb @@ -835,17 +835,17 @@ "source": [ "# create a new Session object and load all arrays, axes, groups and metadata \n", "# from all CSV files located in the passed directory\n", - "csv_dir = get_example_filepath('population_session')\n", + "csv_dir = get_example_filepath('demography_eurostat')\n", "session = Session(csv_dir)\n", "\n", "# create a new Session object and load all arrays, axes, groups and metadata\n", "# stored in the passed Excel file\n", - "filepath_excel = get_example_filepath('population_session.xlsx')\n", + "filepath_excel = get_example_filepath('demography_eurostat.xlsx')\n", "session = Session(filepath_excel)\n", "\n", "# create a new Session object and load all arrays, axes, groups and metadata\n", "# stored in the passed HDF5 file\n", - "filepath_hdf = get_example_filepath('population_session.h5')\n", + "filepath_hdf = get_example_filepath('demography_eurostat.h5')\n", "session = Session(filepath_hdf)\n", "\n", "print(session.summary())" diff --git a/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipyml b/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipyml index 3961c6cd6..2b1e2ef07 100644 --- a/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipyml +++ b/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipyml @@ -9,7 +9,6 @@ cells: import warnings warnings.filterwarnings("ignore", message=r'.*numpy.dtype size changed*') - id: 0 metadata: nbsphinx: hidden @@ -20,7 +19,6 @@ cells: - code: | from larray import * - id: 1 - markdown: | Check the version of LArray: @@ -30,22 +28,15 @@ cells: from larray import __version__ __version__ - id: 2 - markdown: | ## Arithmetic operations -- markdown: | - Import a subset of the test array ``pop``: - - - code: | - # import a 6 x 2 x 2 subset of the 'pop' example array - pop = load_example_data('demography').pop[2016, 'BruCap', 90:95] - pop + arr = ndtest((3, 3)) + arr - id: 3 - markdown: | One can do all usual arithmetic operations on an array, it will apply the operation to all elements individually @@ -53,237 +44,304 @@ cells: - code: | # addition - pop + 200 + arr + 10 - id: 4 - code: | # multiplication - pop * 2 + arr * 2 - id: 5 - code: | - # ** means raising to the power (squaring in this case) - pop ** 2 + # 'true' division + arr / 2 + + +- code: | + # 'floor' division + arr // 2 + + +- markdown: | +
+ **Warning:** Python has two different division operators: + + - the 'true' division (/) always returns a float. + - the 'floor' division (//) returns an integer result (discarding any fractional result). +
- id: 6 - code: | # % means modulo (aka remainder of division) - pop % 10 + arr % 5 + + +- code: | + # ** means raising to the power + arr ** 3 - id: 7 - markdown: | - More interestingly, it also works between two arrays + More interestingly, binary operators as above also works between two arrays: - code: | - # load mortality equivalent array - mortality = load_example_data('demography').qx[2016, 'BruCap', 90:95] + # load the 'demography_eurostat' dataset + demo_eurostat = load_example_data('demography_eurostat') - # compute number of deaths - death = pop * mortality - death + # extract the 'pop' array + pop = demo_eurostat.pop + pop + + +- code: | + aggregation_matrix = LArray([[1, 0, 0], [0, 1, 1]], axes=(Axis('country=Belgium,France+Germany'), pop.country)) + aggregation_matrix + + +- code: | + # @ means matrix product + aggregation_matrix @ pop['Male'] - id: 8 - markdown: |
**Note:** Be careful when mixing different data types. - You can use the method ``astype`` to change the data type of an array. + You can use the method [astype](../_generated/larray.LArray.astype.rst#larray.LArray.astype) to change the data type of an array.
- code: | - # to be sure to get number of deaths as integers - # one can use .astype() method - death = (pop * mortality).astype(int) - death + aggregation_matrix = LArray([[1, 0, 0], [0, 0.5, 0.5]], axes=(Axis('country=Belgium,France+Germany/2'), pop.country)) + aggregation_matrix + + +- code: | + aggregation_matrix @ pop['Male'] + + +- code: | + # force the resulting matrix to be an integer matrix + (aggregation_matrix @ pop['Male']).astype(int) - id: 9 - markdown: | -
- **Warning:** Operations between two arrays only works when they have compatible axes (i.e. same labels). - However, it can be override but at your own risk. - In that case only the position on the axis is used and not the labels. -
+ ### Axis order does not matter much (except for output) + + You can do operations between arrays having different axes order. + The axis order of the result is the same as the left array - code: | - pop[90:92] * mortality[93:95] + # extract the 'births' array + births = demo_eurostat.births + + # let's change the order of axes of the 'births' array + births_transposed = births.transpose() + births_transposed - id: 10 - code: | - pop[90:92] * mortality[93:95].ignore_labels('age') + # LArray doesn't care of axes order when performing + # arithmetic operations between arrays + pop + births_transposed - id: 11 - markdown: | - ### Boolean Operations + ### Axes must be compatible + + Arithmetic operations between two arrays only works when they have compatible axes (i.e. same labels). - code: | - pop2 = pop.copy() - pop2['F'] = -pop2['F'] - pop2 + # the 'pop' and 'births' have compatible axes + pop + births - id: 12 - code: | - # testing for equality is done using == (a single = assigns the value) - pop == pop2 + # Now, let's replace the country names by the country codes + births_codes = births.set_labels('country', ['BE', 'FR', 'DE']) + births_codes - id: 13 - code: | - # testing for inequality - pop != pop2 + # arithmetic operations between arrays + # having incompatible axes raise an error + try: + pop + births_codes + except Exception as e: + print(type(e).__name__, e) - id: 14 -- code: | - # what was our original array like again? - pop +- markdown: | +
+ **Warning:** Operations between two arrays only works when they have compatible axes (i.e. same labels) but this behavior can be override via the [ignore_labels](../_generated/larray.LArray.ignore_labels.rst#larray.LArray.ignore_labels) method. + In that case only the position on the axis is used and not the labels. + Using this method is done at your own risk. +
- id: 15 - code: | - # & means (boolean array) and - (pop >= 500) & (pop <= 1000) + # use the .ignore_labels() method on axis 'country' + # to avoid the incompatible axes error (risky) + pop + births_codes.ignore_labels('country') - id: 16 + metadata: + scrolled: true -- code: | - # | means (boolean array) or - (pop < 500) | (pop > 1000) +- markdown: | + ### Extra Or Missing Axes (Broadcasting) - id: 17 - markdown: | - ### Arithmetic operations with missing axes + The condition that axes must be compatible only applies on common axes. + Arithmetic operations between two arrays can be performed even if the second array has extra or missing axes compared to the first one: - code: | - pop.sum('age') + # let's define a 'multiplicator' vector with + # one value defined for each gender + multiplicator = LArray([-1, 1], axes=pop.gender) + multiplicator - id: 18 - code: | - # arr has 3 dimensions - pop.info + # the multiplication below has been propagated to the + # 'country' and 'time' axes. + # This behavior is called broadcasting + pop * multiplicator - id: 19 -- code: | - # and arr.sum(age) has two - pop.sum('age').info +- markdown: | + ### Boolean Operations + + Python comparison operators are: + + | Operator | Meaning | + |-----------|-------------------------| + |``==`` | equal | + |``!=`` | not equal | + |``>`` | greater than | + |``>=`` | greater than or equal | + |``<`` | less than | + |``<=`` | less than or equal | + + Applying a comparison operator on an array returns a boolean array: - id: 20 - code: | - # you can do operation with missing axes so this works - pop / pop.sum('age') + # test which values are greater than 10 millions + pop > 10e6 - id: 21 - markdown: | - ### Axis order does not matter much (except for output) + Comparison operations can be combined using Python bitwise operators: - You can do operations between arrays having different axes order. - The axis order of the result is the same as the left array + | Operator | Meaning | + |----------|------------------------------------- | + | & | and | + | \| | or | + | ~ | not | - code: | - pop + # test which values are greater than 10 millions and less than 40 millions + (pop > 10e6) & (pop < 40e6) - id: 22 - code: | - # let us change the order of axes - pop_transposed = pop.T - pop_transposed + # test which values are less than 10 millions or greater than 40 millions + (pop < 10e6) | (pop > 40e6) - id: 23 - code: | - # mind blowing - pop_transposed + pop + # test which values are not less than 10 millions + ~(pop < 10e6) - id: 24 - markdown: | - ## Aggregates - - Calculate the sum along an axis: + The returned boolean array can then be used in selections and assignments: - code: | - pop = load_example_data('demography').pop[2016, 'BruCap'] - pop.sum('age') + pop_copy = pop.copy() + + # set all values greater than 40 millions to 40 millions + pop_copy[pop_copy > 40e6] = 40e6 + pop_copy - id: 25 - markdown: | - or along all axes except one by appending `_by` to the aggregation function + Boolean operations can be made between arrays: - code: | - pop[90:95].sum_by('age') - # is equivalent to - pop[90:95].sum('sex', 'nat') + # test where the two arrays have the same values + pop == pop_copy - id: 26 - markdown: | - Calculate the sum along one group: + To test if all values between are equals, use the [equals](../_generated/larray.LArray.equals.rst#larray.LArray.equals) method: - code: | - teens = pop.age[10:20] + pop.equals(pop_copy) + + +- markdown: | + ## Aggregates - pop.sum(teens) + LArray provides many aggregation functions. The list is given in the [Aggregation Functions](../api.rst#aggregation-functions) subsection of the [API Reference](../api.rst) page. + + Aggregation operations can be performed on axes or groups. Axes and groups can be mixed. + + The main rules are: + + - Axes are separated by commas ``,`` + - Groups belonging to the same axis are grouped inside parentheses () - id: 27 - markdown: | - Calculate the sum along two groups: + Calculate the sum along an axis: - code: | - pensioners = pop.age[67:] - - # groups from the same axis must be grouped in a tuple - pop.sum((teens, pensioners)) + pop.sum('gender') - id: 28 - markdown: | - Mixing axes and groups in aggregations: + or several axes (axes are separated by commas ``,``): - code: | - pop.sum((teens, pensioners), 'nat') + pop.sum('country', 'gender') - id: 29 - markdown: | - ### More On Aggregations + Calculate the sum along all axes except one by appending `_by` to the aggregation function: + + +- code: | + pop.sum_by('time') - markdown: | - There are many other aggregation functions: + Calculate the sum along groups (the groups belonging to the same axis must grouped inside parentheses ()): + + +- code: | + even_years = pop.time[2014::2] >> 'even_years' + odd_years = pop.time[2013::2] >> 'odd_years' - - mean, min, max, median, percentile, var (variance), std (standard - deviation) - - labelofmin, labelofmax (label indirect minimum/maxium -- labels where the - value is minimum/maximum) - - indexofmin, indexofmax (positional indirect minimum/maxium -- position - along axis where the value is minimum/maximum) - - cumsum, cumprod (cumulative sum, cumulative product) + pop.sum((odd_years, even_years)) + + +- markdown: | + Mixing axes and groups in aggregations: + + +- code: | + pop.sum('gender', (odd_years, even_years)) # The lines below here may be deleted if you do not need them. @@ -303,25 +361,10 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true nbformat: 4 nbformat_minor: 2 -# --------------------------------------------------------------------------- -data: - [{execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}] - diff --git a/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipynb b/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipynb index 8e9fead86..b6ea22d88 100644 --- a/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipynb +++ b/doc/source/tutorial/tutorial_arithmetic_op_and_aggregation.ipynb @@ -60,11 +60,21 @@ "## Arithmetic operations\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "arr = ndtest((3, 3))\n", + "arr" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Import a subset of the test array ``pop``:\n" + "One can do all usual arithmetic operations on an array, it will apply the operation to all elements individually\n" ] }, { @@ -73,16 +83,18 @@ "metadata": {}, "outputs": [], "source": [ - "# import a 6 x 2 x 2 subset of the 'pop' example array\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 90:95]\n", - "pop" + "# addition\n", + "arr + 10" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "One can do all usual arithmetic operations on an array, it will apply the operation to all elements individually\n" + "# multiplication\n", + "arr * 2" ] }, { @@ -91,8 +103,8 @@ "metadata": {}, "outputs": [], "source": [ - "# addition\n", - "pop + 200" + "# 'true' division\n", + "arr / 2" ] }, { @@ -101,8 +113,20 @@ "metadata": {}, "outputs": [], "source": [ - "# multiplication\n", - "pop * 2" + "# 'floor' division\n", + "arr // 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "**Warning:** Python has two different division operators: \n", + "\n", + "- the 'true' division (/) always returns a float.\n", + "- the 'floor' division (//) returns an integer result (discarding any fractional result).\n", + "
" ] }, { @@ -111,8 +135,8 @@ "metadata": {}, "outputs": [], "source": [ - "# ** means raising to the power (squaring in this case)\n", - "pop ** 2" + "# % means modulo (aka remainder of division)\n", + "arr % 5" ] }, { @@ -121,15 +145,15 @@ "metadata": {}, "outputs": [], "source": [ - "# % means modulo (aka remainder of division)\n", - "pop % 10" + "# ** means raising to the power\n", + "arr ** 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "More interestingly, it also works between two arrays\n" + "More interestingly, binary operators as above also works between two arrays:\n" ] }, { @@ -138,22 +162,22 @@ "metadata": {}, "outputs": [], "source": [ - "# load mortality equivalent array\n", - "mortality = load_example_data('demography').qx[2016, 'BruCap', 90:95]\n", + "# load the 'demography_eurostat' dataset\n", + "demo_eurostat = load_example_data('demography_eurostat')\n", "\n", - "# compute number of deaths\n", - "death = pop * mortality\n", - "death" + "# extract the 'pop' array\n", + "pop = demo_eurostat.pop\n", + "pop" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "
\n", - "**Note:** Be careful when mixing different data types.\n", - "You can use the method ``astype`` to change the data type of an array.\n", - "
\n" + "aggregation_matrix = LArray([[1, 0, 0], [0, 1, 1]], axes=(Axis('country=Belgium,France+Germany'), pop.country))\n", + "aggregation_matrix" ] }, { @@ -162,20 +186,17 @@ "metadata": {}, "outputs": [], "source": [ - "# to be sure to get number of deaths as integers\n", - "# one can use .astype() method\n", - "death = (pop * mortality).astype(int)\n", - "death" + "# @ means matrix product\n", + "aggregation_matrix @ pop['Male']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "**Warning:** Operations between two arrays only works when they have compatible axes (i.e. same labels).\n", - "However, it can be override but at your own risk.\n", - "In that case only the position on the axis is used and not the labels.\n", + "
\n", + "**Note:** Be careful when mixing different data types.\n", + "You can use the method [astype](../_generated/larray.LArray.astype.rst#larray.LArray.astype) to change the data type of an array.\n", "
\n" ] }, @@ -185,7 +206,8 @@ "metadata": {}, "outputs": [], "source": [ - "pop[90:92] * mortality[93:95]" + "aggregation_matrix = LArray([[1, 0, 0], [0, 0.5, 0.5]], axes=(Axis('country=Belgium,France+Germany/2'), pop.country))\n", + "aggregation_matrix" ] }, { @@ -194,14 +216,27 @@ "metadata": {}, "outputs": [], "source": [ - "pop[90:92] * mortality[93:95].ignore_labels('age')" + "aggregation_matrix @ pop['Male']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# force the resulting matrix to be an integer matrix\n", + "(aggregation_matrix @ pop['Male']).astype(int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Boolean Operations\n" + "### Axis order does not matter much (except for output)\n", + "\n", + "You can do operations between arrays having different axes order.\n", + "The axis order of the result is the same as the left array\n" ] }, { @@ -210,9 +245,12 @@ "metadata": {}, "outputs": [], "source": [ - "pop2 = pop.copy()\n", - "pop2['F'] = -pop2['F']\n", - "pop2" + "# extract the 'births' array\n", + "births = demo_eurostat.births\n", + "\n", + "# let's change the order of axes of the 'births' array\n", + "births_transposed = births.transpose()\n", + "births_transposed" ] }, { @@ -221,18 +259,18 @@ "metadata": {}, "outputs": [], "source": [ - "# testing for equality is done using == (a single = assigns the value)\n", - "pop == pop2" + "# LArray doesn't care of axes order when performing \n", + "# arithmetic operations between arrays\n", + "pop + births_transposed" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# testing for inequality\n", - "pop != pop2" + "### Axes must be compatible\n", + "\n", + "Arithmetic operations between two arrays only works when they have compatible axes (i.e. same labels)." ] }, { @@ -241,8 +279,8 @@ "metadata": {}, "outputs": [], "source": [ - "# what was our original array like again?\n", - "pop" + "# the 'pop' and 'births' have compatible axes\n", + "pop + births" ] }, { @@ -251,8 +289,9 @@ "metadata": {}, "outputs": [], "source": [ - "# & means (boolean array) and\n", - "(pop >= 500) & (pop <= 1000)" + "# Now, let's replace the country names by the country codes\n", + "births_codes = births.set_labels('country', ['BE', 'FR', 'DE'])\n", + "births_codes" ] }, { @@ -261,24 +300,51 @@ "metadata": {}, "outputs": [], "source": [ - "# | means (boolean array) or\n", - "(pop < 500) | (pop > 1000)" + "# arithmetic operations between arrays \n", + "# having incompatible axes raise an error\n", + "try:\n", + " pop + births_codes\n", + "except Exception as e:\n", + " print(type(e).__name__, e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Arithmetic operations with missing axes\n" + "
\n", + " **Warning:** Operations between two arrays only works when they have compatible axes (i.e. same labels) but this behavior can be override via the [ignore_labels](../_generated/larray.LArray.ignore_labels.rst#larray.LArray.ignore_labels) method.\n", + "In that case only the position on the axis is used and not the labels.\n", + "Using this method is done at your own risk.\n", + "
\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ - "pop.sum('age')" + "# use the .ignore_labels() method on axis 'country'\n", + "# to avoid the incompatible axes error (risky)\n", + "pop + births_codes.ignore_labels('country')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Extra Or Missing Axes (Broadcasting)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The condition that axes must be compatible only applies on common axes. \n", + "Arithmetic operations between two arrays can be performed even if the second array has extra or missing axes compared to the first one:" ] }, { @@ -287,8 +353,10 @@ "metadata": {}, "outputs": [], "source": [ - "# arr has 3 dimensions\n", - "pop.info" + "# let's define a 'multiplicator' vector with \n", + "# one value defined for each gender\n", + "multiplicator = LArray([-1, 1], axes=pop.gender)\n", + "multiplicator" ] }, { @@ -297,8 +365,30 @@ "metadata": {}, "outputs": [], "source": [ - "# and arr.sum(age) has two\n", - "pop.sum('age').info" + "# the multiplication below has been propagated to the \n", + "# 'country' and 'time' axes.\n", + "# This behavior is called broadcasting\n", + "pop * multiplicator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Boolean Operations\n", + "\n", + "Python comparison operators are: \n", + "\n", + "| Operator | Meaning |\n", + "|-----------|-------------------------|\n", + "|``==`` | equal | \n", + "|``!=`` | not equal | \n", + "|``>`` | greater than | \n", + "|``>=`` | greater than or equal | \n", + "|``<`` | less than | \n", + "|``<=`` | less than or equal |\n", + "\n", + "Applying a comparison operator on an array returns a boolean array:" ] }, { @@ -307,18 +397,21 @@ "metadata": {}, "outputs": [], "source": [ - "# you can do operation with missing axes so this works\n", - "pop / pop.sum('age')" + "# test which values are greater than 10 millions\n", + "pop > 10e6" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Axis order does not matter much (except for output)\n", + "Comparison operations can be combined using Python bitwise operators:\n", "\n", - "You can do operations between arrays having different axes order.\n", - "The axis order of the result is the same as the left array\n" + "| Operator | Meaning |\n", + "|----------|------------------------------------- |\n", + "| & | and |\n", + "| \\| | or |\n", + "| ~ | not |" ] }, { @@ -327,7 +420,8 @@ "metadata": {}, "outputs": [], "source": [ - "pop" + "# test which values are greater than 10 millions and less than 40 millions\n", + "(pop > 10e6) & (pop < 40e6)" ] }, { @@ -336,9 +430,8 @@ "metadata": {}, "outputs": [], "source": [ - "# let us change the order of axes\n", - "pop_transposed = pop.T\n", - "pop_transposed" + "# test which values are less than 10 millions or greater than 40 millions\n", + "(pop < 10e6) | (pop > 40e6)" ] }, { @@ -347,17 +440,15 @@ "metadata": {}, "outputs": [], "source": [ - "# mind blowing\n", - "pop_transposed + pop" + "# test which values are not less than 10 millions\n", + "~(pop < 10e6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Aggregates\n", - "\n", - "Calculate the sum along an axis:\n" + "The returned boolean array can then be used in selections and assignments:" ] }, { @@ -366,15 +457,18 @@ "metadata": {}, "outputs": [], "source": [ - "pop = load_example_data('demography').pop[2016, 'BruCap']\n", - "pop.sum('age')" + "pop_copy = pop.copy()\n", + "\n", + "# set all values greater than 40 millions to 40 millions\n", + "pop_copy[pop_copy > 40e6] = 40e6\n", + "pop_copy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "or along all axes except one by appending `_by` to the aggregation function\n" + "Boolean operations can be made between arrays:" ] }, { @@ -383,16 +477,15 @@ "metadata": {}, "outputs": [], "source": [ - "pop[90:95].sum_by('age')\n", - "# is equivalent to\n", - "pop[90:95].sum('sex', 'nat')" + "# test where the two arrays have the same values\n", + "pop == pop_copy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Calculate the sum along one group:\n" + "To test if all values between are equals, use the [equals](../_generated/larray.LArray.equals.rst#larray.LArray.equals) method:" ] }, { @@ -401,16 +494,30 @@ "metadata": {}, "outputs": [], "source": [ - "teens = pop.age[10:20]\n", + "pop.equals(pop_copy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aggregates\n", + "\n", + "LArray provides many aggregation functions. The list is given in the [Aggregation Functions](../api.rst#aggregation-functions) subsection of the [API Reference](../api.rst) page.\n", + "\n", + "Aggregation operations can be performed on axes or groups. Axes and groups can be mixed. \n", "\n", - "pop.sum(teens)" + "The main rules are: \n", + "\n", + "- Axes are separated by commas ``,``\n", + "- Groups belonging to the same axis are grouped inside parentheses ()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Calculate the sum along two groups:\n" + "Calculate the sum along an axis:" ] }, { @@ -419,17 +526,14 @@ "metadata": {}, "outputs": [], "source": [ - "pensioners = pop.age[67:]\n", - "\n", - "# groups from the same axis must be grouped in a tuple\n", - "pop.sum((teens, pensioners))" + "pop.sum('gender')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Mixing axes and groups in aggregations:\n" + "or several axes (axes are separated by commas ``,``):" ] }, { @@ -438,29 +542,58 @@ "metadata": {}, "outputs": [], "source": [ - "pop.sum((teens, pensioners), 'nat')" + "pop.sum('country', 'gender')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### More On Aggregations\n" + "Calculate the sum along all axes except one by appending `_by` to the aggregation function:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop.sum_by('time')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "There are many other aggregation functions:\n", + "Calculate the sum along groups (the groups belonging to the same axis must grouped inside parentheses ()):\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "even_years = pop.time[2014::2] >> 'even_years'\n", + "odd_years = pop.time[2013::2] >> 'odd_years'\n", "\n", - "- mean, min, max, median, percentile, var (variance), std (standard\n", - " deviation)\n", - "- labelofmin, labelofmax (label indirect minimum/maxium -- labels where the\n", - " value is minimum/maximum)\n", - "- indexofmin, indexofmax (positional indirect minimum/maxium -- position\n", - " along axis where the value is minimum/maximum)\n", - "- cumsum, cumprod (cumulative sum, cumulative product)\n" + "pop.sum((odd_years, even_years))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mixing axes and groups in aggregations:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop.sum('gender', (odd_years, even_years))" ] } ], @@ -481,7 +614,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_indexing.ipyml b/doc/source/tutorial/tutorial_indexing.ipyml index 1771a6a8e..848da0b61 100644 --- a/doc/source/tutorial/tutorial_indexing.ipyml +++ b/doc/source/tutorial/tutorial_indexing.ipyml @@ -9,7 +9,6 @@ cells: import warnings warnings.filterwarnings("ignore", message=r'.*numpy.dtype size changed*') - id: 0 metadata: nbsphinx: hidden @@ -20,7 +19,6 @@ cells: - code: | from larray import * - id: 1 - markdown: | Check the version of LArray: @@ -30,7 +28,6 @@ cells: from larray import __version__ __version__ - id: 2 - markdown: | Import the test array ``pop``: @@ -38,10 +35,9 @@ cells: - code: | # let's start with - pop = load_example_data('demography').pop + pop = load_example_data('demography_eurostat').pop pop - id: 3 - markdown: | ## Selecting (Subsets) @@ -58,103 +54,101 @@ cells: - code: | - # here we select the value associated with Belgian women - # of age 50 from Brussels region for the year 2015 - pop[2015, 'BruCap', 50, 'F', 'BE'] + pop['Belgium', 'Female', 2017] - id: 4 - markdown: | - Continue with selecting a subset using slices and lists of labels + As long as there is no ambiguity (i.e. axes sharing one or several same label(s)), the order of indexing does not matter. + So you usually do not care/have to remember about axes positions during computation. It only matters for output. - code: | - # here we select the subset associated with Belgian women of age 50, 51 and 52 - # from Brussels region for the years 2010 to 2016 - pop[2010:2016, 'BruCap', 50:52, 'F', 'BE'] + # order of index doesn't matter + pop['Female', 2017, 'Belgium'] + + +- markdown: | + Selecting a subset is done by using slices or lists of labels: - id: 5 - code: | - # slices bounds are optional: - # if not given start is assumed to be the first label and stop is the last one. - # Here we select all years starting from 2010 - pop[2010:, 'BruCap', 50:52, 'F', 'BE'] + pop[['Belgium', 'Germany'], 2014:2016] + + +- markdown: | + Slices bounds are optional: + if not given, start is assumed to be the first label and stop is the last one. - id: 6 - code: | - # Slices can also have a step (defaults to 1), to take every Nth labels - # Here we select all even years starting from 2010 - pop[2010::2, 'BruCap', 50:52, 'F', 'BE'] + # select all years starting from 2015 + pop[2015:] - id: 7 - code: | - # one can also use list of labels to take non-contiguous labels. - # Here we select years 2008, 2010, 2013 and 2015 - pop[[2008, 2010, 2013, 2015], 'BruCap', 50:52, 'F', 'BE'] + # select all first years until 2015 + pop[:2015] - id: 8 - markdown: | - The order of indexing does not matter either, so you usually do not care/have to remember about axes positions during computation. It only matters for output. + Slices can also have a step (defaults to 1), to take every Nth labels: - code: | - # order of index doesn't matter - pop['F', 'BE', 'BruCap', [2008, 2010, 2013, 2015], 50:52] + # select all even years starting from 2014 + pop[2014::2] - id: 9 - markdown: |
- **Warning:** Selecting by labels as above works well as long as there is no ambiguity. - When two or more axes have common labels, it may lead to a crash. + **Warning:** Selecting by labels as in above examples works well as long as there is no ambiguity. + When two or more axes have common labels, it leads to a crash. The solution is then to precise to which axis belong the labels.
- code: | - # let us now create an array with the same labels on several axes - age, weight, size = Axis('age=0..80'), Axis('weight=0..120'), Axis('size=0..200') + immigration = load_example_data('demography_eurostat').immigration - arr_ws = ndtest([age, weight, size]) + # the 'immigration' array has two axes (country and citizenship) which share the same labels + immigration - id: 10 - code: | - # let's try to select teenagers with size between 1 m 60 and 1 m 65 and weight > 80 kg. - # In this case the subset is ambiguous and this results in an error: - arr_ws[10:18, :80, 160:165] + # LArray doesn't use the position of the labels used inside the brackets + # to determine the corresponding axes. Instead LArray will try to guess the + # corresponding axis for each label whatever is its position. + # Then, if a label is shared by two or more axes, LArray will not be able + # to choose between the possible axes and will raise an error. + try: + immigration['Belgium', 'Netherlands'] + except Exception as e: + print(type(e).__name__, ':', e) - id: 11 - code: | # the solution is simple. You need to precise the axes on which you make a selection - arr_ws[age[10:18], weight[:80], size[160:165]] + immigration[immigration.country['Belgium'], immigration.citizenship['Netherlands']] - id: 12 - markdown: | ### Ambiguous Cases - Specifying Axes Using The Special Variable X - When selecting, assiging or using aggregate functions, an axis can be - refered via the special variable ``X``: + When selecting, assigning or using aggregate functions, an axis can be + referred via the special variable ``X``: - - pop[X.age[:20]] - - pop.sum(X.age) + - pop[X.time[2015:]] + - pop.sum(X.time) - This gives you acces to axes of the array you are manipulating. The main + This gives you access to axes of the array you are manipulating. The main drawback of using ``X`` is that you lose the autocompletion available from many editors. It only works with non-anonymous axes for which names do not contain whitespaces or special characters. - code: | - # the previous example could have been also written as - arr_ws[X.age[10:18], X.weight[:80], X.size[160:165]] + # the previous example can also be written as + immigration[X.country['Belgium'], X.citizenship['Netherlands']] - id: 13 - markdown: | ### Selecting by Indices @@ -173,23 +167,19 @@ cells: - code: | - # here we select the subset associated with Belgian women of age 50, 51 and 52 - # from Brussels region for the first 3 years - pop[X.time.i[:3], 'BruCap', 50:52, 'F', 'BE'] + # select the last year + pop[X.time.i[-1]] - id: 14 - code: | # same but for the last 3 years - pop[X.time.i[-3:], 'BruCap', 50:52, 'F', 'BE'] + pop[X.time.i[-3:]] - id: 15 - code: | - # using list of indices - pop[X.time.i[-9,-7,-4,-2], 'BruCap', 50:52, 'F', 'BE'] + # using a list of indices + pop[X.time.i[0, 2, 4]] - id: 16 - markdown: |
@@ -198,16 +188,17 @@ cells: - code: | - # with labels (3 is included) - pop[2015, 'BruCap', X.age[:3], 'F', 'BE'] + year = 2015 + + # with labels + pop[X.time[:year]] - id: 17 - code: | - # with indices (3 is out) - pop[2015, 'BruCap', X.age.i[:3], 'F', 'BE'] + # with indices (i.e. using the .i[indices] syntax) + index_year = pop.time.index(year) + pop[X.time.i[:index_year]] - id: 18 - markdown: | You can use ``.i[]`` selection directly on array instead of axes. @@ -215,231 +206,195 @@ cells: - code: | - # here we select the last year and first 3 ages - # equivalent to: pop.i[-1, :, :3, :, :] - pop.i[-1, :, :3] + # select first country and last three years + pop.i[0, :, -3:] - id: 19 - markdown: | ### Using Groups In Selections - code: | - teens = pop.age[10:20] + even_years = pop.time[2014::2] - pop[2015, 'BruCap', teens, 'F', 'BE'] + pop[even_years] - id: 20 - markdown: | - ## Assigning subsets - - ### Assigning A Value + ## Boolean Filtering - Assign a value to a subset + Boolean filtering can be used to extract subsets. Filtering can be done on axes: - code: | - # let's take a smaller array - pop = load_example_data('demography').pop[2016, 'BruCap', 100:105] - pop2 = pop - pop2 + # select even years + pop[X.time % 2 == 0] + + +- markdown: | + or data: - id: 21 - code: | - # set all data corresponding to age >= 102 to 0 - pop2[102:] = 0 - pop2 + # select population for the year 2017 + pop_2017 = pop[2017] + + # select all data with a value greater than 30 million + pop_2017[pop_2017 > 30e6] - id: 22 - markdown: | - One very important gotcha though... - -
- **Warning:** Modifying a slice of an array in-place like we did above should be done with care otherwise you could have **unexpected effects**. The reason is that taking a **slice** subset of an array does not return a copy of that array, but rather a view on that array. To avoid such behavior, use ``.copy()`` method. +
+ **Note:** Be aware that after boolean filtering, several axes may have merged.
- - Remember: - - - taking a slice subset of an array is extremely fast (no data is - copied) - - if one modifies that subset in-place, one also **modifies the - original array** - - **.copy()** returns a copy of the subset (takes speed and memory) but - allows you to change the subset without modifying the original array - in the same time -- code: | - # indeed, data from the original array have also changed - pop +- markdown: | + Arrays can also be used to create boolean filters: - id: 23 - code: | - # the right way - pop = load_example_data('demography').pop[2016, 'BruCap', 100:105] - - pop2 = pop.copy() - pop2[102:] = 0 - pop2 + start_year = LArray([2015, 2016, 2017], axes=pop.country) + start_year - id: 24 - code: | - # now, data from the original array have not changed this time - pop + pop[X.time >= start_year] - id: 25 - markdown: | - ### Assigning Arrays And Broadcasting + ## Assigning subsets - Instead of a value, we can also assign an array to a subset. In that - case, that array can have less axes than the target but those which are - present must be compatible with the subset being targeted. - - -- code: | - sex, nat = Axis('sex=M,F'), Axis('nat=BE,FO') - new_value = LArray([[1, -1], [2, -2]],[sex, nat]) - new_value + ### Assigning A Value + + Assigning a value to a subset is simple: - id: 26 - code: | - # this assigns 1, -1 to Belgian, Foreigner men - # and 2, -2 to Belgian, Foreigner women for all - # people older than 100 - pop[102:] = new_value + pop[2017] = 0 pop - id: 27 - markdown: | -
- **Warning:** The array being assigned must have compatible axes (i.e. same axes names and same labels) with the target subset. -
+ Now, let's store a subset in a new variable and modify it: - code: | - # assume we define the following array with shape 3 x 2 x 2 - new_value = zeros(['age=100..102', sex, nat]) - new_value + # store the data associated with the year 2016 in a new variable + pop_2016 = pop[2016] + pop_2016 - id: 28 - code: | - # now let's try to assign the previous array in a subset from age 103 to 105 - pop[103:105] = new_value - - id: 29 - -- code: | - # but this works - pop[100:102] = new_value + # now, we modify the new variable + pop_2016['Belgium'] = 0 + + # and we can see that the original array has been also modified pop - id: 30 - markdown: | - ## Boolean Filtering + One very important gotcha though... - Boolean filtering can be use to extract subsets. - - -- code: | - #Let's focus on population living in Brussels during the year 2016 - pop = load_example_data('demography').pop[2016, 'BruCap'] +
+ **Warning:** Storing a subset of an array in a new variable and modifying it after may also impact the original array. The reason is that selecting a contiguous subset of the data does not return a copy of the selected subset, but rather a view on a subset of the array. To avoid such behavior, use the ``.copy()`` method. +
- # here we select all males and females with age less than 5 and 10 respectively - subset = pop[((X.sex == 'H') & (X.age <= 5)) | ((X.sex == 'F') & (X.age <= 10))] - subset + Remember: + + - taking a contiguous subset of an array is extremely fast (no data is copied) + - if one modifies that subset, one also **modifies the original array** + - **.copy()** returns a copy of the subset (takes speed and memory) but + allows you to change the subset without modifying the original array + in the same time - id: 31 - markdown: | -
- **Note:** Be aware that after boolean filtering, several axes may have merged. -
+ The same warning apply for entire arrays: - code: | - # 'age' and 'sex' axes have been merged together - subset.info + # reload the 'pop' array + pop = load_example_data('demography_eurostat').pop + + # create a second 'pop2' variable + pop2 = pop + pop2 - id: 32 -- markdown: | - This may be not what you because previous selections on merged axes are no longer valid +- code: | + # set all data corresponding to the year 2017 to 0 + pop2[2017] = 0 + pop2 - code: | - # now let's try to calculate the proportion of females with age less than 10 - subset['F'].sum() / pop['F'].sum() + # and now take a look of what happened to the original array 'pop' + # after modifying the 'pop2' array + pop - id: 33 - markdown: | - Therefore, it is sometimes more useful to not select, but rather set to 0 (or another value) non matching elements +
+ **Warning:** The syntax ``new_array = old_array`` does not create a new array but rather an 'alias' variable. To actually create a new array as a copy of a previous one, the ``.copy()`` method must be called. +
- code: | - subset = pop.copy() - subset[((X.sex == 'F') & (X.age > 10))] = 0 - subset['F', :20] + # reload the 'pop' array + pop = load_example_data('demography_eurostat').pop + + # copy the 'pop' array and store the copy in a new variable + pop2 = pop.copy() + + # modify the copy + pop2[2017] = 0 + pop2 - id: 34 - code: | - # now we can calculate the proportion of females with age less than 10 - subset['F'].sum() / pop['F'].sum() + # the data from the original array have not been modified + pop - id: 35 - markdown: | - Boolean filtering can also mix axes and arrays. Example above could also have been written as - - -- code: | - age_limit = sequence('sex=M,F', initial=5, inc=5) - age_limit - - id: 36 - -- code: | - age = pop.axes['age'] - (age <= age_limit)[:20] + ### Assigning Arrays And Broadcasting + + Instead of a value, we can also assign an array to a subset. In that + case, that array can have less axes than the target but those which are + present must be compatible with the subset being targeted. - id: 37 - code: | - subset = pop.copy() - subset[X.age > age_limit] = 0 - subset['F'].sum() / pop['F'].sum() + # select population for the year 2015 + pop_2015 = pop[2015] + + # propagate population for the year 2015 to all next years + pop[2016:] = pop_2015 + + pop - id: 38 - markdown: | - Finally, you can choose to filter on data instead of axes +
+ **Warning:** The array being assigned must have compatible axes (i.e. same axes names and same labels) with the target subset. +
- code: | - # let's focus on females older than 90 - subset = pop['F', 90:110].copy() - subset + # replace 'Male' and 'Female' labels by 'M' and 'F' + pop_2015 = pop_2015.set_labels('gender', 'M,F') + pop_2015 - id: 39 - code: | - # here we set to 0 all data < 10 - subset[subset < 10] = 0 - subset + # now let's try to repeat the assignement operation above with the new labels. + # An error is raised because of incompatible axes + try: + pop[2016:] = pop_2015 + except Exception as e: + print(type(e).__name__, ':', e) - id: 40 # The lines below here may be deleted if you do not need them. # --------------------------------------------------------------------------- @@ -458,30 +413,10 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true nbformat: 4 nbformat_minor: 2 -# --------------------------------------------------------------------------- -data: - [{execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}] - diff --git a/doc/source/tutorial/tutorial_indexing.ipynb b/doc/source/tutorial/tutorial_indexing.ipynb index d1c6e1cab..e0a116f46 100644 --- a/doc/source/tutorial/tutorial_indexing.ipynb +++ b/doc/source/tutorial/tutorial_indexing.ipynb @@ -67,7 +67,7 @@ "outputs": [], "source": [ "# let's start with\n", - "pop = load_example_data('demography').pop\n", + "pop = load_example_data('demography_eurostat').pop\n", "pop" ] }, @@ -97,16 +97,15 @@ "metadata": {}, "outputs": [], "source": [ - "# here we select the value associated with Belgian women\n", - "# of age 50 from Brussels region for the year 2015\n", - "pop[2015, 'BruCap', 50, 'F', 'BE']" + "pop['Belgium', 'Female', 2017]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Continue with selecting a subset using slices and lists of labels\n" + "As long as there is no ambiguity (i.e. axes sharing one or several same label(s)), the order of indexing does not matter. \n", + "So you usually do not care/have to remember about axes positions during computation. It only matters for output." ] }, { @@ -115,9 +114,15 @@ "metadata": {}, "outputs": [], "source": [ - "# here we select the subset associated with Belgian women of age 50, 51 and 52\n", - "# from Brussels region for the years 2010 to 2016\n", - "pop[2010:2016, 'BruCap', 50:52, 'F', 'BE']" + "# order of index doesn't matter\n", + "pop['Female', 2017, 'Belgium']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Selecting a subset is done by using slices or lists of labels:" ] }, { @@ -126,10 +131,15 @@ "metadata": {}, "outputs": [], "source": [ - "# slices bounds are optional:\n", - "# if not given start is assumed to be the first label and stop is the last one.\n", - "# Here we select all years starting from 2010\n", - "pop[2010:, 'BruCap', 50:52, 'F', 'BE']" + "pop[['Belgium', 'Germany'], 2014:2016]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Slices bounds are optional:\n", + "if not given, start is assumed to be the first label and stop is the last one." ] }, { @@ -138,9 +148,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Slices can also have a step (defaults to 1), to take every Nth labels\n", - "# Here we select all even years starting from 2010\n", - "pop[2010::2, 'BruCap', 50:52, 'F', 'BE']" + "# select all years starting from 2015\n", + "pop[2015:]" ] }, { @@ -149,16 +158,15 @@ "metadata": {}, "outputs": [], "source": [ - "# one can also use list of labels to take non-contiguous labels.\n", - "# Here we select years 2008, 2010, 2013 and 2015\n", - "pop[[2008, 2010, 2013, 2015], 'BruCap', 50:52, 'F', 'BE']" + "# select all first years until 2015\n", + "pop[:2015]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The order of indexing does not matter either, so you usually do not care/have to remember about axes positions during computation. It only matters for output.\n" + "Slices can also have a step (defaults to 1), to take every Nth labels:" ] }, { @@ -167,8 +175,8 @@ "metadata": {}, "outputs": [], "source": [ - "# order of index doesn't matter\n", - "pop['F', 'BE', 'BruCap', [2008, 2010, 2013, 2015], 50:52]" + "# select all even years starting from 2014\n", + "pop[2014::2]" ] }, { @@ -176,8 +184,8 @@ "metadata": {}, "source": [ "
\n", - "**Warning:** Selecting by labels as above works well as long as there is no ambiguity.\n", - " When two or more axes have common labels, it may lead to a crash.\n", + "**Warning:** Selecting by labels as in above examples works well as long as there is no ambiguity.\n", + " When two or more axes have common labels, it leads to a crash.\n", " The solution is then to precise to which axis belong the labels.\n", "
\n" ] @@ -188,10 +196,10 @@ "metadata": {}, "outputs": [], "source": [ - "# let us now create an array with the same labels on several axes\n", - "age, weight, size = Axis('age=0..80'), Axis('weight=0..120'), Axis('size=0..200')\n", + "immigration = load_example_data('demography_eurostat').immigration\n", "\n", - "arr_ws = ndtest([age, weight, size])" + "# the 'immigration' array has two axes (country and citizenship) which share the same labels\n", + "immigration" ] }, { @@ -200,9 +208,15 @@ "metadata": {}, "outputs": [], "source": [ - "# let's try to select teenagers with size between 1 m 60 and 1 m 65 and weight > 80 kg.\n", - "# In this case the subset is ambiguous and this results in an error:\n", - "arr_ws[10:18, :80, 160:165]" + "# LArray doesn't use the position of the labels used inside the brackets \n", + "# to determine the corresponding axes. Instead LArray will try to guess the \n", + "# corresponding axis for each label whatever is its position.\n", + "# Then, if a label is shared by two or more axes, LArray will not be able \n", + "# to choose between the possible axes and will raise an error.\n", + "try:\n", + " immigration['Belgium', 'Netherlands']\n", + "except Exception as e:\n", + " print(type(e).__name__, ':', e)" ] }, { @@ -212,7 +226,7 @@ "outputs": [], "source": [ "# the solution is simple. You need to precise the axes on which you make a selection\n", - "arr_ws[age[10:18], weight[:80], size[160:165]]" + "immigration[immigration.country['Belgium'], immigration.citizenship['Netherlands']]" ] }, { @@ -221,13 +235,13 @@ "source": [ "### Ambiguous Cases - Specifying Axes Using The Special Variable X\n", "\n", - "When selecting, assiging or using aggregate functions, an axis can be\n", - "refered via the special variable ``X``:\n", + "When selecting, assigning or using aggregate functions, an axis can be\n", + "referred via the special variable ``X``:\n", "\n", - "- pop[X.age[:20]]\n", - "- pop.sum(X.age)\n", + "- pop[X.time[2015:]]\n", + "- pop.sum(X.time)\n", "\n", - "This gives you acces to axes of the array you are manipulating. The main\n", + "This gives you access to axes of the array you are manipulating. The main\n", "drawback of using ``X`` is that you lose the autocompletion available from\n", "many editors. It only works with non-anonymous axes for which names do not contain whitespaces or special characters.\n" ] @@ -238,8 +252,8 @@ "metadata": {}, "outputs": [], "source": [ - "# the previous example could have been also written as\n", - "arr_ws[X.age[10:18], X.weight[:80], X.size[160:165]]" + "# the previous example can also be written as\n", + "immigration[X.country['Belgium'], X.citizenship['Netherlands']]" ] }, { @@ -270,9 +284,8 @@ "metadata": {}, "outputs": [], "source": [ - "# here we select the subset associated with Belgian women of age 50, 51 and 52\n", - "# from Brussels region for the first 3 years\n", - "pop[X.time.i[:3], 'BruCap', 50:52, 'F', 'BE']" + "# select the last year\n", + "pop[X.time.i[-1]]" ] }, { @@ -282,7 +295,7 @@ "outputs": [], "source": [ "# same but for the last 3 years\n", - "pop[X.time.i[-3:], 'BruCap', 50:52, 'F', 'BE']" + "pop[X.time.i[-3:]]" ] }, { @@ -291,8 +304,8 @@ "metadata": {}, "outputs": [], "source": [ - "# using list of indices\n", - "pop[X.time.i[-9,-7,-4,-2], 'BruCap', 50:52, 'F', 'BE']" + "# using a list of indices\n", + "pop[X.time.i[0, 2, 4]]" ] }, { @@ -310,8 +323,10 @@ "metadata": {}, "outputs": [], "source": [ - "# with labels (3 is included)\n", - "pop[2015, 'BruCap', X.age[:3], 'F', 'BE']" + "year = 2015\n", + "\n", + "# with labels\n", + "pop[X.time[:year]]" ] }, { @@ -320,8 +335,9 @@ "metadata": {}, "outputs": [], "source": [ - "# with indices (3 is out)\n", - "pop[2015, 'BruCap', X.age.i[:3], 'F', 'BE']" + "# with indices (i.e. using the .i[indices] syntax)\n", + "index_year = pop.time.index(year)\n", + "pop[X.time.i[:index_year]]" ] }, { @@ -338,9 +354,8 @@ "metadata": {}, "outputs": [], "source": [ - "# here we select the last year and first 3 ages\n", - "# equivalent to: pop.i[-1, :, :3, :, :]\n", - "pop.i[-1, :, :3]" + "# select first country and last three years\n", + "pop.i[0, :, -3:]" ] }, { @@ -356,20 +371,18 @@ "metadata": {}, "outputs": [], "source": [ - "teens = pop.age[10:20]\n", + "even_years = pop.time[2014::2]\n", "\n", - "pop[2015, 'BruCap', teens, 'F', 'BE']" + "pop[even_years]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Assigning subsets\n", - "\n", - "### Assigning A Value\n", + "## Boolean Filtering\n", "\n", - "Assign a value to a subset\n" + "Boolean filtering can be used to extract subsets. Filtering can be done on axes:" ] }, { @@ -378,10 +391,15 @@ "metadata": {}, "outputs": [], "source": [ - "# let's take a smaller array\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 100:105]\n", - "pop2 = pop\n", - "pop2" + "# select even years\n", + "pop[X.time % 2 == 0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or data:" ] }, { @@ -390,40 +408,27 @@ "metadata": {}, "outputs": [], "source": [ - "# set all data corresponding to age >= 102 to 0\n", - "pop2[102:] = 0\n", - "pop2" + "# select population for the year 2017\n", + "pop_2017 = pop[2017]\n", + "\n", + "# select all data with a value greater than 30 million\n", + "pop_2017[pop_2017 > 30e6]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "One very important gotcha though...\n", - "\n", - "
\n", - "**Warning:** Modifying a slice of an array in-place like we did above should be done with care otherwise you could have **unexpected effects**. The reason is that taking a **slice** subset of an array does not return a copy of that array, but rather a view on that array. To avoid such behavior, use ``.copy()`` method.\n", - "
\n", - "\n", - "Remember:\n", - "\n", - "- taking a slice subset of an array is extremely fast (no data is\n", - " copied)\n", - "- if one modifies that subset in-place, one also **modifies the\n", - " original array**\n", - "- **.copy()** returns a copy of the subset (takes speed and memory) but\n", - " allows you to change the subset without modifying the original array\n", - " in the same time\n" + "
\n", + "**Note:** Be aware that after boolean filtering, several axes may have merged.\n", + "
\n" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# indeed, data from the original array have also changed\n", - "pop" + "Arrays can also be used to create boolean filters:\n" ] }, { @@ -432,12 +437,8 @@ "metadata": {}, "outputs": [], "source": [ - "# the right way\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 100:105]\n", - "\n", - "pop2 = pop.copy()\n", - "pop2[102:] = 0\n", - "pop2" + "start_year = LArray([2015, 2016, 2017], axes=pop.country)\n", + "start_year" ] }, { @@ -446,30 +447,18 @@ "metadata": {}, "outputs": [], "source": [ - "# now, data from the original array have not changed this time\n", - "pop" + "pop[X.time >= start_year]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Assigning Arrays And Broadcasting\n", + "## Assigning subsets\n", "\n", - "Instead of a value, we can also assign an array to a subset. In that\n", - "case, that array can have less axes than the target but those which are\n", - "present must be compatible with the subset being targeted.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sex, nat = Axis('sex=M,F'), Axis('nat=BE,FO')\n", - "new_value = LArray([[1, -1], [2, -2]],[sex, nat])\n", - "new_value" + "### Assigning A Value\n", + "\n", + "Assigning a value to a subset is simple:\n" ] }, { @@ -478,10 +467,7 @@ "metadata": {}, "outputs": [], "source": [ - "# this assigns 1, -1 to Belgian, Foreigner men\n", - "# and 2, -2 to Belgian, Foreigner women for all\n", - "# people older than 100\n", - "pop[102:] = new_value\n", + "pop[2017] = 0\n", "pop" ] }, @@ -489,20 +475,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "**Warning:** The array being assigned must have compatible axes (i.e. same axes names and same labels) with the target subset.\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# assume we define the following array with shape 3 x 2 x 2\n", - "new_value = zeros(['age=100..102', sex, nat])\n", - "new_value" + "Now, let's store a subset in a new variable and modify it:" ] }, { @@ -511,8 +484,9 @@ "metadata": {}, "outputs": [], "source": [ - "# now let's try to assign the previous array in a subset from age 103 to 105\n", - "pop[103:105] = new_value" + "# store the data associated with the year 2016 in a new variable\n", + "pop_2016 = pop[2016]\n", + "pop_2016" ] }, { @@ -521,8 +495,10 @@ "metadata": {}, "outputs": [], "source": [ - "# but this works\n", - "pop[100:102] = new_value\n", + "# now, we modify the new variable\n", + "pop_2016['Belgium'] = 0\n", + "\n", + "# and we can see that the original array has been also modified\n", "pop" ] }, @@ -530,32 +506,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Boolean Filtering\n", + "One very important gotcha though...\n", "\n", - "Boolean filtering can be use to extract subsets.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Let's focus on population living in Brussels during the year 2016\n", - "pop = load_example_data('demography').pop[2016, 'BruCap']\n", + "
\n", + "**Warning:** Storing a subset of an array in a new variable and modifying it after may also impact the original array. The reason is that selecting a contiguous subset of the data does not return a copy of the selected subset, but rather a view on a subset of the array. To avoid such behavior, use the ``.copy()`` method.\n", + "
\n", "\n", - "# here we select all males and females with age less than 5 and 10 respectively\n", - "subset = pop[((X.sex == 'H') & (X.age <= 5)) | ((X.sex == 'F') & (X.age <= 10))]\n", - "subset" + "Remember:\n", + "\n", + "- taking a contiguous subset of an array is extremely fast (no data is copied)\n", + "- if one modifies that subset, one also **modifies the original array**\n", + "- **.copy()** returns a copy of the subset (takes speed and memory) but\n", + " allows you to change the subset without modifying the original array\n", + " in the same time\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "**Note:** Be aware that after boolean filtering, several axes may have merged.\n", - "
\n" + "The same warning apply for entire arrays:" ] }, { @@ -564,15 +534,23 @@ "metadata": {}, "outputs": [], "source": [ - "# 'age' and 'sex' axes have been merged together\n", - "subset.info" + "# reload the 'pop' array\n", + "pop = load_example_data('demography_eurostat').pop\n", + "\n", + "# create a second 'pop2' variable\n", + "pop2 = pop\n", + "pop2" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "This may be not what you because previous selections on merged axes are no longer valid\n" + "# set all data corresponding to the year 2017 to 0\n", + "pop2[2017] = 0\n", + "pop2" ] }, { @@ -581,15 +559,18 @@ "metadata": {}, "outputs": [], "source": [ - "# now let's try to calculate the proportion of females with age less than 10\n", - "subset['F'].sum() / pop['F'].sum()" + "# and now take a look of what happened to the original array 'pop'\n", + "# after modifying the 'pop2' array\n", + "pop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Therefore, it is sometimes more useful to not select, but rather set to 0 (or another value) non matching elements\n" + "
\n", + "**Warning:** The syntax ``new_array = old_array`` does not create a new array but rather an 'alias' variable. To actually create a new array as a copy of a previous one, the ``.copy()`` method must be called.\n", + "
" ] }, { @@ -598,9 +579,15 @@ "metadata": {}, "outputs": [], "source": [ - "subset = pop.copy()\n", - "subset[((X.sex == 'F') & (X.age > 10))] = 0\n", - "subset['F', :20]" + "# reload the 'pop' array\n", + "pop = load_example_data('demography_eurostat').pop\n", + "\n", + "# copy the 'pop' array and store the copy in a new variable\n", + "pop2 = pop.copy()\n", + "\n", + "# modify the copy\n", + "pop2[2017] = 0\n", + "pop2" ] }, { @@ -609,35 +596,19 @@ "metadata": {}, "outputs": [], "source": [ - "# now we can calculate the proportion of females with age less than 10\n", - "subset['F'].sum() / pop['F'].sum()" + "# the data from the original array have not been modified\n", + "pop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Boolean filtering can also mix axes and arrays. Example above could also have been written as\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "age_limit = sequence('sex=M,F', initial=5, inc=5)\n", - "age_limit" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "age = pop.axes['age']\n", - "(age <= age_limit)[:20]" + "### Assigning Arrays And Broadcasting\n", + "\n", + "Instead of a value, we can also assign an array to a subset. In that\n", + "case, that array can have less axes than the target but those which are\n", + "present must be compatible with the subset being targeted.\n" ] }, { @@ -646,16 +617,22 @@ "metadata": {}, "outputs": [], "source": [ - "subset = pop.copy()\n", - "subset[X.age > age_limit] = 0\n", - "subset['F'].sum() / pop['F'].sum()" + "# select population for the year 2015\n", + "pop_2015 = pop[2015]\n", + "\n", + "# propagate population for the year 2015 to all next years\n", + "pop[2016:] = pop_2015\n", + "\n", + "pop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Finally, you can choose to filter on data instead of axes\n" + "
\n", + "**Warning:** The array being assigned must have compatible axes (i.e. same axes names and same labels) with the target subset.\n", + "
\n" ] }, { @@ -664,9 +641,9 @@ "metadata": {}, "outputs": [], "source": [ - "# let's focus on females older than 90\n", - "subset = pop['F', 90:110].copy()\n", - "subset" + "# replace 'Male' and 'Female' labels by 'M' and 'F'\n", + "pop_2015 = pop_2015.set_labels('gender', 'M,F')\n", + "pop_2015" ] }, { @@ -675,9 +652,12 @@ "metadata": {}, "outputs": [], "source": [ - "# here we set to 0 all data < 10\n", - "subset[subset < 10] = 0\n", - "subset" + "# now let's try to repeat the assignement operation above with the new labels.\n", + "# An error is raised because of incompatible axes\n", + "try:\n", + " pop[2016:] = pop_2015\n", + "except Exception as e:\n", + " print(type(e).__name__, ':', e)" ] } ], @@ -698,7 +678,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_miscellaneous.ipyml b/doc/source/tutorial/tutorial_miscellaneous.ipyml index c8c2ae69a..216668502 100644 --- a/doc/source/tutorial/tutorial_miscellaneous.ipyml +++ b/doc/source/tutorial/tutorial_miscellaneous.ipyml @@ -1,7 +1,7 @@ cells: - markdown: | - # Miscellaneous (other interesting array functions) + # Some Useful Functions - code: | @@ -9,7 +9,6 @@ cells: import warnings warnings.filterwarnings("ignore", message=r'.*numpy.dtype size changed*') - id: 0 metadata: nbsphinx: hidden @@ -20,7 +19,6 @@ cells: - code: | from larray import * - id: 1 - markdown: | Check the version of LArray: @@ -30,120 +28,114 @@ cells: from larray import __version__ __version__ - id: 2 - -- markdown: | - Import a subset of the test array ``pop``: - - code: | - # import a 6 x 2 x 2 subset of the 'pop' example array - pop = load_example_data('demography').pop[2016, 'BruCap', 100:105] + # load 'demography_eurostat' dataset + demo_eurostat = load_example_data('demography_eurostat') + + # extract the 'pop' array from the dataset + pop = demo_eurostat.pop pop - id: 3 - markdown: | ### with total - Add totals to one axis + Add totals to one or several axes: - code: | - pop.with_total('sex', label='B') + pop.with_total('gender', label='Total') - id: 4 - markdown: | - Add totals to all axes at once + See [with_total](../_generated/larray.LArray.with_total.rst#larray.LArray.with_total) for more details and examples. -- code: | - # by default label is 'total' - pop.with_total() - - id: 5 - - markdown: | ### where - where can be used to apply some computation depending on a condition + The ``where`` function can be used to apply some computation depending on a condition: - code: | # where(condition, value if true, value if false) - where(pop < 10, 0, -pop) + where(pop < pop.mean('time'), -pop, pop) + + +- markdown: | + See [where](../_generated/larray.where.rst#larray.where) for more details and examples. - id: 6 - markdown: | ### clip - Set all data between a certain range + Set all data between a certain range: - code: | - # clip(min, max) - # values below 10 are set to 10 and values above 50 are set to 50 - pop.clip(10, 50) + # values below 10 millions are set to 10 millions + pop.clip(minval=10**7) - id: 7 -- markdown: | - ### divnot0 - - Replace division by 0 to 0 +- code: | + # values above 40 millions are set to 40 millions + pop.clip(maxval=4*10**7) - code: | - pop['BE'] / pop['FO'] + # values below 10 millions are set to 10 millions and + # values above 40 millions are set to 40 millions + pop.clip(10**7, 4*10**7) - id: 8 -- code: | - # divnot0 replaces results of division by 0 by 0. - # Using it should be done with care though - # because it can hide a real error in your data. - pop['BE'].divnot0(pop['FO']) +- markdown: | + See [clip](../_generated/larray.LArray.clip.rst#larray.LArray.clip) for more details and examples. - id: 9 - markdown: | - ### diff + ### divnot0 - The ``diff`` method calculates the n-th order discrete difference along a given axis. - The first order difference is given by out[n+1] = in[n + 1] - in[n] along the given axis. + Replace division by 0 by 0: - code: | - pop = load_example_data('demography').pop[2005:2015, 'BruCap', 50] - pop + divisor = ones(pop.axes, dtype=int) + divisor['Male'] = 0 + divisor - id: 10 - code: | - # calculates 'pop[year+1] - pop[year]' - pop.diff('time') + pop / divisor - id: 11 - code: | - # calculates 'pop[year+2] - pop[year]' - pop.diff('time', d=2) + # we use astype(int) since the divnot0 method + # returns a float array in this case while + # we want an integer array + pop.divnot0(divisor).astype(int) + + +- markdown: | + See [divnot0](../_generated/larray.LArray.divnot0.rst#larray.LArray.divnot0) for more details and examples. - id: 12 - markdown: | ### ratio + + The ``ratio`` (``rationot0``) method returns an array with all values divided by the sum of values along given axes: - code: | - pop.ratio('nat') + pop.ratio('gender') # which is equivalent to - pop / pop.sum('nat') + pop / pop.sum('gender') + + +- markdown: | + See [ratio](../_generated/larray.LArray.ratio.rst#larray.LArray.ratio) and [rationot0](../_generated/larray.LArray.rationot0.rst#larray.LArray.rationot0) for more details and examples. - id: 13 - markdown: | ### percents @@ -151,20 +143,55 @@ cells: - code: | # or, if you want the previous ratios in percents - pop.percent('nat') + pop.percent('gender') + + +- markdown: | + See [percent](../_generated/larray.LArray.percent.rst#larray.LArray.percent) for more details and examples. + + +- markdown: | + ### diff + + The ``diff`` method calculates the n-th order discrete difference along a given axis. + + The first order difference is given by ``out[n+1] = in[n+1] - in[n]`` along the given axis. + + +- code: | + # calculates 'diff[year+1] = pop[year+1] - pop[year]' + pop.diff('time') + + +- code: | + # calculates 'diff[year+2] = pop[year+2] - pop[year]' + pop.diff('time', d=2) + + +- code: | + # calculates 'diff[year] = pop[year+1] - pop[year]' + pop.diff('time', label='lower') + + +- markdown: | + See [diff](../_generated/larray.LArray.diff.rst#larray.LArray.diff) for more details and examples. - id: 14 - markdown: | ### growth\_rate - using the same principle than `diff` + The ``growth_rate`` method calculates the growth along a given axis. + + It is roughly equivalent to ``a.diff(axis, d, label) / a[axis.i[:-d]]``: - code: | pop.growth_rate('time') - id: 15 + +- markdown: | + See [growth_rate](../_generated/larray.LArray.growth_rate.rst#larray.LArray.growth_rate) for more details and examples. + - markdown: | ### shift @@ -175,32 +202,22 @@ cells: - code: | pop.shift('time') - id: 16 - code: | # when shift is applied on an (increasing) time axis, # it effectively brings "past" data into the future - pop.shift('time').ignore_labels('time') == pop[2005:2014].ignore_labels('time') + pop_shifted = pop.shift('time') + stack({'pop_shifted_2014': pop_shifted[2014], 'pop_2013': pop[2013]}, 'array') - id: 17 -- code: | - # this is mostly useful when you want to do operations between the past and now - # as an example, here is an alternative implementation of the .diff method seen above: - pop.i[1:] - pop.shift('time') +- markdown: | + See [shift](../_generated/larray.LArray.shift.rst#larray.LArray.shift) for more details and examples. - id: 18 - markdown: | - ### Misc other interesting functions + ### Other interesting functions - There are a lot more interesting functions available: - - - round, floor, ceil, trunc, - - exp, log, log10, - - sqrt, absolute, nan_to_num, isnan, isinf, inverse, - - sin, cos, tan, arcsin, arccos, arctan - - and many many more... + There are a lot more interesting functions that you can find in the API reference in sections [Aggregation Functions](../api.rst#aggregation-functions), [Miscellaneous](../api.rst#miscellaneous) and [Utility Functions](../api.rst#utility-functions). # The lines below here may be deleted if you do not need them. @@ -220,21 +237,10 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true nbformat: 4 nbformat_minor: 2 -# --------------------------------------------------------------------------- -data: - [{execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}] - diff --git a/doc/source/tutorial/tutorial_miscellaneous.ipynb b/doc/source/tutorial/tutorial_miscellaneous.ipynb index 2a723fe8b..78f5c4251 100644 --- a/doc/source/tutorial/tutorial_miscellaneous.ipynb +++ b/doc/source/tutorial/tutorial_miscellaneous.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Miscellaneous (other interesting array functions)\n" + "# Some Useful Functions\n" ] }, { @@ -53,21 +53,17 @@ "__version__" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Import a subset of the test array ``pop``:\n" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# import a 6 x 2 x 2 subset of the 'pop' example array\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 100:105]\n", + "# load 'demography_eurostat' dataset\n", + "demo_eurostat = load_example_data('demography_eurostat')\n", + "\n", + "# extract the 'pop' array from the dataset \n", + "pop = demo_eurostat.pop\n", "pop" ] }, @@ -77,7 +73,7 @@ "source": [ "### with total\n", "\n", - "Add totals to one axis\n" + "Add totals to one or several axes:\n" ] }, { @@ -86,14 +82,23 @@ "metadata": {}, "outputs": [], "source": [ - "pop.with_total('sex', label='B')" + "pop.with_total('gender', label='Total')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Add totals to all axes at once\n" + "See [with_total](../_generated/larray.LArray.with_total.rst#larray.LArray.with_total) for more details and examples.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### where\n", + "\n", + "The ``where`` function can be used to apply some computation depending on a condition:\n" ] }, { @@ -102,17 +107,24 @@ "metadata": {}, "outputs": [], "source": [ - "# by default label is 'total'\n", - "pop.with_total()" + "# where(condition, value if true, value if false)\n", + "where(pop < pop.mean('time'), -pop, pop)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### where\n", + "See [where](../_generated/larray.where.rst#larray.where) for more details and examples.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### clip\n", "\n", - "where can be used to apply some computation depending on a condition\n" + "Set all data between a certain range:\n" ] }, { @@ -121,17 +133,18 @@ "metadata": {}, "outputs": [], "source": [ - "# where(condition, value if true, value if false)\n", - "where(pop < 10, 0, -pop)" + "# values below 10 millions are set to 10 millions\n", + "pop.clip(minval=10**7)" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### clip\n", - "\n", - "Set all data between a certain range\n" + "# values above 40 millions are set to 40 millions\n", + "pop.clip(maxval=4*10**7)" ] }, { @@ -140,9 +153,16 @@ "metadata": {}, "outputs": [], "source": [ - "# clip(min, max)\n", - "# values below 10 are set to 10 and values above 50 are set to 50\n", - "pop.clip(10, 50)" + "# values below 10 millions are set to 10 millions and \n", + "# values above 40 millions are set to 40 millions\n", + "pop.clip(10**7, 4*10**7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [clip](../_generated/larray.LArray.clip.rst#larray.LArray.clip) for more details and examples.\n" ] }, { @@ -151,7 +171,7 @@ "source": [ "### divnot0\n", "\n", - "Replace division by 0 to 0\n" + "Replace division by 0 by 0:\n" ] }, { @@ -160,7 +180,9 @@ "metadata": {}, "outputs": [], "source": [ - "pop['BE'] / pop['FO']" + "divisor = ones(pop.axes, dtype=int)\n", + "divisor['Male'] = 0\n", + "divisor" ] }, { @@ -169,20 +191,35 @@ "metadata": {}, "outputs": [], "source": [ - "# divnot0 replaces results of division by 0 by 0.\n", - "# Using it should be done with care though\n", - "# because it can hide a real error in your data.\n", - "pop['BE'].divnot0(pop['FO'])" + "pop / divisor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# we use astype(int) since the divnot0 method \n", + "# returns a float array in this case while \n", + "# we want an integer array\n", + "pop.divnot0(divisor).astype(int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### diff\n", + "See [divnot0](../_generated/larray.LArray.divnot0.rst#larray.LArray.divnot0) for more details and examples.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ratio\n", "\n", - "The ``diff`` method calculates the n-th order discrete difference along a given axis.\n", - "The first order difference is given by out[n+1] = in[n + 1] - in[n] along the given axis.\n" + "The ``ratio`` (``rationot0``) method returns an array with all values divided by the sum of values along given axes:" ] }, { @@ -191,18 +228,24 @@ "metadata": {}, "outputs": [], "source": [ - "pop = load_example_data('demography').pop[2005:2015, 'BruCap', 50]\n", - "pop" + "pop.ratio('gender')\n", + "\n", + "# which is equivalent to\n", + "pop / pop.sum('gender')" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# calculates 'pop[year+1] - pop[year]'\n", - "pop.diff('time')" + "See [ratio](../_generated/larray.LArray.ratio.rst#larray.LArray.ratio) and [rationot0](../_generated/larray.LArray.rationot0.rst#larray.LArray.rationot0) for more details and examples.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### percents\n" ] }, { @@ -211,15 +254,26 @@ "metadata": {}, "outputs": [], "source": [ - "# calculates 'pop[year+2] - pop[year]'\n", - "pop.diff('time', d=2)" + "# or, if you want the previous ratios in percents\n", + "pop.percent('gender')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### ratio\n" + "See [percent](../_generated/larray.LArray.percent.rst#larray.LArray.percent) for more details and examples.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### diff\n", + "\n", + "The ``diff`` method calculates the n-th order discrete difference along a given axis.\n", + "\n", + "The first order difference is given by ``out[n+1] = in[n+1] - in[n]`` along the given axis.\n" ] }, { @@ -228,17 +282,18 @@ "metadata": {}, "outputs": [], "source": [ - "pop.ratio('nat')\n", - "\n", - "# which is equivalent to\n", - "pop / pop.sum('nat')" + "# calculates 'diff[year+1] = pop[year+1] - pop[year]'\n", + "pop.diff('time')" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### percents\n" + "# calculates 'diff[year+2] = pop[year+2] - pop[year]'\n", + "pop.diff('time', d=2)" ] }, { @@ -247,8 +302,15 @@ "metadata": {}, "outputs": [], "source": [ - "# or, if you want the previous ratios in percents\n", - "pop.percent('nat')" + "# calculates 'diff[year] = pop[year+1] - pop[year]'\n", + "pop.diff('time', label='lower')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [diff](../_generated/larray.LArray.diff.rst#larray.LArray.diff) for more details and examples.\n" ] }, { @@ -257,7 +319,9 @@ "source": [ "### growth\\_rate\n", "\n", - "using the same principle than `diff`\n" + "The ``growth_rate`` method calculates the growth along a given axis.\n", + " \n", + "It is roughly equivalent to ``a.diff(axis, d, label) / a[axis.i[:-d]]``:\n" ] }, { @@ -269,6 +333,13 @@ "pop.growth_rate('time')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [growth_rate](../_generated/larray.LArray.growth_rate.rst#larray.LArray.growth_rate) for more details and examples.\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -295,33 +366,24 @@ "source": [ "# when shift is applied on an (increasing) time axis,\n", "# it effectively brings \"past\" data into the future\n", - "pop.shift('time').ignore_labels('time') == pop[2005:2014].ignore_labels('time')" + "pop_shifted = pop.shift('time')\n", + "stack({'pop_shifted_2014': pop_shifted[2014], 'pop_2013': pop[2013]}, 'array')" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# this is mostly useful when you want to do operations between the past and now\n", - "# as an example, here is an alternative implementation of the .diff method seen above:\n", - "pop.i[1:] - pop.shift('time')" + "See [shift](../_generated/larray.LArray.shift.rst#larray.LArray.shift) for more details and examples.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Misc other interesting functions\n", - "\n", - "There are a lot more interesting functions available:\n", + "### Other interesting functions\n", "\n", - "- round, floor, ceil, trunc,\n", - "- exp, log, log10,\n", - "- sqrt, absolute, nan_to_num, isnan, isinf, inverse,\n", - "- sin, cos, tan, arcsin, arccos, arctan\n", - "- and many many more...\n" + "There are a lot more interesting functions that you can find in the API reference in sections [Aggregation Functions](../api.rst#aggregation-functions), [Miscellaneous](../api.rst#miscellaneous) and [Utility Functions](../api.rst#utility-functions).\n" ] } ], @@ -342,7 +404,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_plotting.ipyml b/doc/source/tutorial/tutorial_plotting.ipyml index 4cf5bdb77..2c3272daa 100644 --- a/doc/source/tutorial/tutorial_plotting.ipyml +++ b/doc/source/tutorial/tutorial_plotting.ipyml @@ -9,7 +9,6 @@ cells: import warnings warnings.filterwarnings("ignore", message=r'.*numpy.dtype size changed*') - id: 0 metadata: nbsphinx: hidden @@ -20,7 +19,6 @@ cells: - code: | from larray import * - id: 1 - markdown: | Check the version of LArray: @@ -30,27 +28,27 @@ cells: from larray import __version__ __version__ - id: 2 - markdown: | Import a subset of the test array ``pop``: - code: | - # import a 6 x 2 x 2 subset of the 'pop' example array - pop = load_example_data('demography').pop[2016, 'BruCap', 90:95] + # load 'demography_eurostat' dataset + demo_eurostat = load_example_data('demography_eurostat') + + # extract the 'pop' array from the dataset + pop = demo_eurostat.pop pop - id: 3 - markdown: | - Inline matplotlib: + Inline matplotlib (required in notebooks): - code: | - %matplotlib inline + %matplotlib inline - id: 4 - markdown: | Create a plot (last axis define the different curves to draw): @@ -59,13 +57,10 @@ cells: - code: | pop.plot() - id: 5 -- code: | - # plot total of both sex - pop.sum('sex').plot() +- markdown: | + See [plot](../_generated/larray.LArray.plot.rst#larray.LArray.plot) for more details and examples. - id: 6 # The lines below here may be deleted if you do not need them. # --------------------------------------------------------------------------- @@ -84,16 +79,10 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true nbformat: 4 nbformat_minor: 2 -# --------------------------------------------------------------------------- -data: - [{execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}] - diff --git a/doc/source/tutorial/tutorial_plotting.ipynb b/doc/source/tutorial/tutorial_plotting.ipynb index b3867582d..d7d37d764 100644 --- a/doc/source/tutorial/tutorial_plotting.ipynb +++ b/doc/source/tutorial/tutorial_plotting.ipynb @@ -66,8 +66,11 @@ "metadata": {}, "outputs": [], "source": [ - "# import a 6 x 2 x 2 subset of the 'pop' example array\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 90:95]\n", + "# load 'demography_eurostat' dataset\n", + "demo_eurostat = load_example_data('demography_eurostat')\n", + "\n", + "# extract the 'pop' array from the dataset \n", + "pop = demo_eurostat.pop\n", "pop" ] }, @@ -75,7 +78,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Inline matplotlib:\n" + "Inline matplotlib (required in notebooks):\n" ] }, { @@ -84,7 +87,7 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline " + "%matplotlib inline" ] }, { @@ -104,13 +107,10 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# plot total of both sex\n", - "pop.sum('sex').plot()" + "See [plot](../_generated/larray.LArray.plot.rst#larray.LArray.plot) for more details and examples." ] } ], @@ -131,7 +131,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_presenting_larray_objects.ipyml b/doc/source/tutorial/tutorial_presenting_larray_objects.ipyml index bf2c9680a..ff4342375 100644 --- a/doc/source/tutorial/tutorial_presenting_larray_objects.ipyml +++ b/doc/source/tutorial/tutorial_presenting_larray_objects.ipyml @@ -330,7 +330,7 @@ cells: # add axes to the session gender = Axis("gender=Male,Female") s_pop.gender = gender - time = Axis("time=2013,2014,2015") + time = Axis("time=2013..2017") s_pop.time = time # add arrays to the session @@ -352,7 +352,7 @@ cells: - code: | gender = Axis("gender=Male,Female") - time = Axis("time=2013,2014,2015") + time = Axis("time=2013..2017") # create and populate a new session in one step # Python <= 3.5 @@ -411,7 +411,7 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.8 + version: 3.7.3 livereveal: autolaunch: false scroll: true diff --git a/doc/source/tutorial/tutorial_presenting_larray_objects.ipynb b/doc/source/tutorial/tutorial_presenting_larray_objects.ipynb index 55af251d7..c98ffb444 100644 --- a/doc/source/tutorial/tutorial_presenting_larray_objects.ipynb +++ b/doc/source/tutorial/tutorial_presenting_larray_objects.ipynb @@ -536,7 +536,7 @@ "# add axes to the session\n", "gender = Axis(\"gender=Male,Female\")\n", "s_pop.gender = gender\n", - "time = Axis(\"time=2013,2014,2015\")\n", + "time = Axis(\"time=2013..2017\")\n", "s_pop.time = time\n", "\n", "# add arrays to the session\n", @@ -566,7 +566,7 @@ "outputs": [], "source": [ "gender = Axis(\"gender=Male,Female\")\n", - "time = Axis(\"time=2013,2014,2015\")\n", + "time = Axis(\"time=2013..2017\")\n", "\n", "# create and populate a new session in one step\n", "# Python <= 3.5\n", @@ -632,7 +632,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_sessions.ipyml b/doc/source/tutorial/tutorial_sessions.ipyml index 8efdd2cb0..77e2a954a 100644 --- a/doc/source/tutorial/tutorial_sessions.ipyml +++ b/doc/source/tutorial/tutorial_sessions.ipyml @@ -43,7 +43,7 @@ cells: - code: | # load a session representing the results of a demographic model - filepath_hdf = get_example_filepath('population_session.h5') + filepath_hdf = get_example_filepath('demography_eurostat.h5') s_pop = Session(filepath_hdf) # print the content of the session @@ -62,13 +62,27 @@ cells: - markdown: | ### Selecting And Filtering Items - To select an item, simply use the syntax ``.``: + Session objects work like ordinary ``dict`` Python objects. To select an item, use the usual syntax ``['']``: + + +- code: | + s_pop['pop'] + + +- markdown: | + A simpler way consists in the use the syntax ``.``: - code: | s_pop.pop +- markdown: | +
+ **Warning:** The syntax ``session_var.item_name`` will work as long as you don't use any special character like ``, ; :`` in the item's name. +
+ + - markdown: | To return a new session with selected items, use the syntax ``[list, of, item, names]``: @@ -93,6 +107,39 @@ cells: s_pop.filter(pattern='[a-k]*') +- markdown: | + ### Iterating over Items + + Like the built-in Python ``dict`` objects, Session objects provide methods to iterate over items: + + +- code: | + # iterate over item names + for key in s_pop.keys(): + print(key) + + +- code: | + # iterate over items + for value in s_pop.values(): + if isinstance(value, LArray): + print(value.info) + else: + print(repr(value)) + print() + + +- code: | + # iterate over names and items + for key, value in s_pop.items(): + if isinstance(value, LArray): + print(key, ':') + print(value.info) + else: + print(key, ':', repr(value)) + print() + + - markdown: | ### Arithmetic Operations On Sessions @@ -112,13 +159,13 @@ cells: - code: | from larray import random - random_multiplicator = random.choice([0.98, 1.0, 1.02], p=[0.15, 0.7, 0.15], axes=s_pop.pop.axes) - random_multiplicator + random_increment = random.choice([-1, 0, 1], p=[0.3, 0.4, 0.3], axes=s_pop.pop.axes) * 1000 + random_increment - code: | - # multiply all variables of a session by a common array - s_pop_rand = s_pop * random_multiplicator + # add some variables of a session by a common array + s_pop_rand = s_pop['pop', 'births', 'deaths'] + random_increment s_pop_rand.pop @@ -141,17 +188,21 @@ cells: - code: | - # force conversion to type int - def as_type_int(array): - return array.astype(int) + # add the next year to all arrays + def add_next_year(array): + if 'time' in array.axes.names: + last_year = array.time.i[-1] + return array.append('time', 0, last_year + 1) + else: + return array - s_pop_rand_int = s_pop_rand.apply(as_type_int) + s_pop_with_next_year = s_pop.apply(add_next_year) print('pop array before calling apply:') - print(s_pop_rand.pop) + print(s_pop.pop) print() print('pop array after calling apply:') - print(s_pop_rand_int.pop) + print(s_pop_with_next_year.pop) - markdown: | @@ -159,15 +210,24 @@ cells: - code: | - # passing the LArray.astype method directly with argument - # dtype defined as int - s_pop_rand_int = s_pop_rand.apply(LArray.astype, dtype=int) + # add the next year to all arrays. + # Use the 'copy_values_from_last_year flag' to indicate + # whether or not to copy values from the last year + def add_next_year(array, copy_values_from_last_year): + if 'time' in array.axes.names: + last_year = array.time.i[-1] + value = array[last_year] if copy_values_from_last_year else 0 + return array.append('time', value, last_year + 1) + else: + return array + + s_pop_with_next_year = s_pop.apply(add_next_year, True) print('pop array before calling apply:') - print(s_pop_rand.pop) + print(s_pop.pop) print() print('pop array after calling apply:') - print(s_pop_rand_int.pop) + print(s_pop_with_next_year.pop) - markdown: | @@ -181,54 +241,62 @@ cells: - markdown: | - [Session objects](../api.rst#session) provide the two methods to compare two sessions: [equals](../_generated/larray.Session.equals.rst#larray.Session.equals) and [element_equals](../_generated/larray.Session.element_equals.rst#larray.Session.element_equals). + [Session objects](../api.rst#session) provide the two methods to compare two sessions: [equals](../_generated/larray.Session.equals.rst#larray.Session.equals) and [element_equals](../_generated/larray.Session.element_equals.rst#larray.Session.element_equals): - The ``equals`` method will return True if **all items** from both sessions are identical, False otherwise: + - The ``equals`` method will return True if **all items** from both sessions are identical, False otherwise. + - The ``element_equals`` method will compare items of two sessions one by one and return an array of boolean values. - code: | # load a session representing the results of a demographic model - filepath_hdf = get_example_filepath('population_session.h5') + filepath_hdf = get_example_filepath('demography_eurostat.h5') s_pop = Session(filepath_hdf) # create a copy of the original session - s_pop_copy = Session(filepath_hdf) - + s_pop_copy = s_pop.copy() + + +- code: | + # 'element_equals' compare arrays one by one + s_pop.element_equals(s_pop_copy) + + +- code: | # 'equals' returns True if all items of the two sessions have exactly the same items s_pop.equals(s_pop_copy) - code: | - # create a copy of the original session but with the array - # 'births' slightly modified for some labels combination - s_pop_alternative = Session(filepath_hdf) - s_pop_alternative.births *= random_multiplicator - - # 'equals' returns False if at least on item of the two sessions are different in values or axes - s_pop.equals(s_pop_alternative) + # slightly modify the 'pop' array for some labels combination + s_pop_copy.pop += random_increment - code: | - # add an array to the session - s_pop_new_output = Session(filepath_hdf) - s_pop_new_output.gender_ratio = s_pop_new_output.pop.ratio('gender') - - # 'equals' returns False if at least on item is not present in the two sessions - s_pop.equals(s_pop_new_output) + # the 'pop' array is different between the two sessions + s_pop.element_equals(s_pop_copy) -- markdown: | - The ``element_equals`` method will compare items of two sessions one by one and return an array of boolean values: +- code: | + # 'equals' returns False if at least one item of the two sessions are different in values or axes + s_pop.equals(s_pop_copy) - code: | - # 'element_equals' compare arrays one by one + # reset the 'copy' session as a copy of the original session + s_pop_copy = s_pop.copy() + + # add an array to the 'copy' session + s_pop_copy.gender_ratio = s_pop_copy.pop.ratio('gender') + + +- code: | + # the 'gender_ratio' array is not present in the original session s_pop.element_equals(s_pop_copy) - code: | - # array 'births' is different between the two sessions - s_pop.element_equals(s_pop_alternative) + # 'equals' returns False if at least one item is not present in the two sessions + s_pop.equals(s_pop_copy) - markdown: | @@ -236,9 +304,17 @@ cells: - code: | - s_same_values = s_pop == s_pop_alternative + # reset the 'copy' session as a copy of the original session + s_pop_copy = s_pop.copy() + + # slightly modify the 'pop' array for some labels combination + s_pop_copy.pop += random_increment + + +- code: | + s_check_same_values = s_pop == s_pop_copy - s_same_values.births + s_check_same_values.pop - markdown: | @@ -246,7 +322,7 @@ cells: - code: | - s_same_values.country + s_check_same_values.time - markdown: | @@ -254,9 +330,9 @@ cells: - code: | - s_different_values = s_pop != s_pop_alternative + s_check_different_values = s_pop != s_pop_copy - s_different_values.births + s_check_different_values.pop - markdown: | @@ -292,7 +368,7 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true diff --git a/doc/source/tutorial/tutorial_sessions.ipynb b/doc/source/tutorial/tutorial_sessions.ipynb index 4ff690d52..7b5bac662 100644 --- a/doc/source/tutorial/tutorial_sessions.ipynb +++ b/doc/source/tutorial/tutorial_sessions.ipynb @@ -78,7 +78,7 @@ "outputs": [], "source": [ "# load a session representing the results of a demographic model\n", - "filepath_hdf = get_example_filepath('population_session.h5')\n", + "filepath_hdf = get_example_filepath('demography_eurostat.h5')\n", "s_pop = Session(filepath_hdf)\n", "\n", "# print the content of the session\n", @@ -108,7 +108,23 @@ "source": [ "### Selecting And Filtering Items\n", "\n", - "To select an item, simply use the syntax ``.``:" + "Session objects work like ordinary ``dict`` Python objects. To select an item, use the usual syntax ``['']``: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s_pop['pop']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A simpler way consists in the use the syntax ``.``:" ] }, { @@ -120,6 +136,15 @@ "s_pop.pop" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " **Warning:** The syntax ``session_var.item_name`` will work as long as you don't use any special character like ``, ; :`` in the item's name.\n", + "
" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -165,6 +190,57 @@ "s_pop.filter(pattern='[a-k]*')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Iterating over Items\n", + "\n", + "Like the built-in Python ``dict`` objects, Session objects provide methods to iterate over items: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# iterate over item names\n", + "for key in s_pop.keys():\n", + " print(key)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# iterate over items\n", + "for value in s_pop.values():\n", + " if isinstance(value, LArray):\n", + " print(value.info)\n", + " else:\n", + " print(repr(value))\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# iterate over names and items\n", + "for key, value in s_pop.items():\n", + " if isinstance(value, LArray):\n", + " print(key, ':')\n", + " print(value.info)\n", + " else:\n", + " print(key, ':', repr(value))\n", + " print()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -200,8 +276,8 @@ "outputs": [], "source": [ "from larray import random\n", - "random_multiplicator = random.choice([0.98, 1.0, 1.02], p=[0.15, 0.7, 0.15], axes=s_pop.pop.axes)\n", - "random_multiplicator" + "random_increment = random.choice([-1, 0, 1], p=[0.3, 0.4, 0.3], axes=s_pop.pop.axes) * 1000\n", + "random_increment" ] }, { @@ -210,8 +286,8 @@ "metadata": {}, "outputs": [], "source": [ - "# multiply all variables of a session by a common array\n", - "s_pop_rand = s_pop * random_multiplicator\n", + "# add some variables of a session by a common array\n", + "s_pop_rand = s_pop['pop', 'births', 'deaths'] + random_increment\n", "\n", "s_pop_rand.pop" ] @@ -250,17 +326,21 @@ "metadata": {}, "outputs": [], "source": [ - "# force conversion to type int\n", - "def as_type_int(array):\n", - " return array.astype(int)\n", + "# add the next year to all arrays\n", + "def add_next_year(array):\n", + " if 'time' in array.axes.names:\n", + " last_year = array.time.i[-1] \n", + " return array.append('time', 0, last_year + 1)\n", + " else:\n", + " return array\n", "\n", - "s_pop_rand_int = s_pop_rand.apply(as_type_int)\n", + "s_pop_with_next_year = s_pop.apply(add_next_year)\n", "\n", "print('pop array before calling apply:')\n", - "print(s_pop_rand.pop)\n", + "print(s_pop.pop)\n", "print()\n", "print('pop array after calling apply:')\n", - "print(s_pop_rand_int.pop)" + "print(s_pop_with_next_year.pop)" ] }, { @@ -276,15 +356,24 @@ "metadata": {}, "outputs": [], "source": [ - "# passing the LArray.astype method directly with argument \n", - "# dtype defined as int\n", - "s_pop_rand_int = s_pop_rand.apply(LArray.astype, dtype=int)\n", + "# add the next year to all arrays.\n", + "# Use the 'copy_values_from_last_year flag' to indicate \n", + "# whether or not to copy values from the last year\n", + "def add_next_year(array, copy_values_from_last_year):\n", + " if 'time' in array.axes.names:\n", + " last_year = array.time.i[-1]\n", + " value = array[last_year] if copy_values_from_last_year else 0\n", + " return array.append('time', value, last_year + 1)\n", + " else:\n", + " return array\n", + "\n", + "s_pop_with_next_year = s_pop.apply(add_next_year, True)\n", "\n", "print('pop array before calling apply:')\n", - "print(s_pop_rand.pop)\n", + "print(s_pop.pop)\n", "print()\n", "print('pop array after calling apply:')\n", - "print(s_pop_rand_int.pop)" + "print(s_pop_with_next_year.pop)" ] }, { @@ -307,9 +396,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[Session objects](../api.rst#session) provide the two methods to compare two sessions: [equals](../_generated/larray.Session.equals.rst#larray.Session.equals) and [element_equals](../_generated/larray.Session.element_equals.rst#larray.Session.element_equals).\n", + "[Session objects](../api.rst#session) provide the two methods to compare two sessions: [equals](../_generated/larray.Session.equals.rst#larray.Session.equals) and [element_equals](../_generated/larray.Session.element_equals.rst#larray.Session.element_equals):\n", "\n", - "The ``equals`` method will return True if **all items** from both sessions are identical, False otherwise:" + "- The ``equals`` method will return True if **all items** from both sessions are identical, False otherwise.\n", + "- The ``element_equals`` method will compare items of two sessions one by one and return an array of boolean values." ] }, { @@ -319,12 +409,29 @@ "outputs": [], "source": [ "# load a session representing the results of a demographic model\n", - "filepath_hdf = get_example_filepath('population_session.h5')\n", + "filepath_hdf = get_example_filepath('demography_eurostat.h5')\n", "s_pop = Session(filepath_hdf)\n", "\n", "# create a copy of the original session\n", - "s_pop_copy = Session(filepath_hdf)\n", - "\n", + "s_pop_copy = s_pop.copy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 'element_equals' compare arrays one by one\n", + "s_pop.element_equals(s_pop_copy)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# 'equals' returns True if all items of the two sessions have exactly the same items\n", "s_pop.equals(s_pop_copy)" ] @@ -335,13 +442,8 @@ "metadata": {}, "outputs": [], "source": [ - "# create a copy of the original session but with the array\n", - "# 'births' slightly modified for some labels combination\n", - "s_pop_alternative = Session(filepath_hdf)\n", - "s_pop_alternative.births *= random_multiplicator\n", - "\n", - "# 'equals' returns False if at least on item of the two sessions are different in values or axes\n", - "s_pop.equals(s_pop_alternative)" + "# slightly modify the 'pop' array for some labels combination\n", + "s_pop_copy.pop += random_increment " ] }, { @@ -350,19 +452,18 @@ "metadata": {}, "outputs": [], "source": [ - "# add an array to the session\n", - "s_pop_new_output = Session(filepath_hdf)\n", - "s_pop_new_output.gender_ratio = s_pop_new_output.pop.ratio('gender')\n", - "\n", - "# 'equals' returns False if at least on item is not present in the two sessions\n", - "s_pop.equals(s_pop_new_output)" + "# the 'pop' array is different between the two sessions\n", + "s_pop.element_equals(s_pop_copy)" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "The ``element_equals`` method will compare items of two sessions one by one and return an array of boolean values:" + "# 'equals' returns False if at least one item of the two sessions are different in values or axes\n", + "s_pop.equals(s_pop_copy)" ] }, { @@ -371,7 +472,20 @@ "metadata": {}, "outputs": [], "source": [ - "# 'element_equals' compare arrays one by one\n", + "# reset the 'copy' session as a copy of the original session\n", + "s_pop_copy = s_pop.copy()\n", + "\n", + "# add an array to the 'copy' session\n", + "s_pop_copy.gender_ratio = s_pop_copy.pop.ratio('gender')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# the 'gender_ratio' array is not present in the original session\n", "s_pop.element_equals(s_pop_copy)" ] }, @@ -381,8 +495,8 @@ "metadata": {}, "outputs": [], "source": [ - "# array 'births' is different between the two sessions\n", - "s_pop.element_equals(s_pop_alternative)" + "# 'equals' returns False if at least one item is not present in the two sessions\n", + "s_pop.equals(s_pop_copy)" ] }, { @@ -398,9 +512,22 @@ "metadata": {}, "outputs": [], "source": [ - "s_same_values = s_pop == s_pop_alternative\n", + "# reset the 'copy' session as a copy of the original session\n", + "s_pop_copy = s_pop.copy()\n", + "\n", + "# slightly modify the 'pop' array for some labels combination\n", + "s_pop_copy.pop += random_increment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s_check_same_values = s_pop == s_pop_copy\n", "\n", - "s_same_values.births" + "s_check_same_values.pop" ] }, { @@ -416,7 +543,7 @@ "metadata": {}, "outputs": [], "source": [ - "s_same_values.country" + "s_check_same_values.time" ] }, { @@ -432,9 +559,9 @@ "metadata": {}, "outputs": [], "source": [ - "s_different_values = s_pop != s_pop_alternative\n", + "s_check_different_values = s_pop != s_pop_copy\n", "\n", - "s_different_values.births " + "s_check_different_values.pop" ] }, { @@ -477,7 +604,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/doc/source/tutorial/tutorial_string_syntax.ipyml b/doc/source/tutorial/tutorial_string_syntax.ipyml new file mode 100644 index 000000000..685eba2e2 --- /dev/null +++ b/doc/source/tutorial/tutorial_string_syntax.ipyml @@ -0,0 +1,162 @@ +cells: + +- markdown: | + # Pythonic VS String Syntax + + +- markdown: | + Import the LArray library: + + +- code: | + from larray import * + + +- markdown: | + Check the version of LArray: + + +- code: | + from larray import __version__ + __version__ + + +- markdown: | + LArray offers two syntaxes to build axes and make selections and aggregations. + The first one is more ``Pythonic`` (uses Python structures) + For example, you can create an *age_category* axis as follows: + + +- code: | + age_category = Axis(["0-9", "10-17", "18-66", "67+"], "age_category") + age_category + + +- markdown: | + The second one consists of using ``strings`` that are parsed. + It is shorter to type. The same *age_category* axis could have been generated as follows: + + +- code: | + age_category = Axis("age_category=0-9,10-17,18-66,67+") + age_category + + +- markdown: | +
+ **Warning:** The drawback of the string syntax is that some characters such as `, ; = : .. [ ] >>` + have a special meaning and cannot be used with the ``String`` syntax. + If you need to work with labels containing such special characters (when importing data from an external source for example), you have to use the ``Pythonic`` syntax which allows to use any character in labels. +
+ + +- markdown: | + ## String Syntax + + +- markdown: | + ### Axes And Arrays creation + + The string syntax allows to easily create axes. + + When creating one axis, the labels are separated using ``,``: + + +- code: | + a = Axis('a=a0,a1,a2,a3') + a + + +- markdown: | + The special syntax ``start..stop`` generates a sequence of labels: + + +- code: | + a = Axis('a=a0..a3') + a + + +- markdown: | + When creating an array, it is possible to define several axes in the same string using ``;`` + + +- code: | + arr = zeros("a=a0..a2; b=b0,b1; c=c0..c5") + arr + + +- markdown: | + ### Selection + + Starting from the array: + + +- code: | + immigration = load_example_data('demography_eurostat').immigration + immigration.info + + +- markdown: | + an example of a selection using the ``Pythonic`` syntax is: + + +- code: | + # since the labels 'Belgium' and 'Netherlands' also exists in the 'citizenship' axis, + # we need to explicitly specify that we want to make a selection over the 'country' axis + immigration_subset = immigration[X.country['Belgium', 'Netherlands'], 'Female', 2015:] + immigration_subset + + +- markdown: | + Using the ``String`` syntax, the same selection becomes: + + +- code: | + immigration_subset = immigration['country[Belgium,Netherlands]', 'Female', 2015:] + immigration_subset + + +- markdown: | + ### Aggregation + + +- markdown: | + An example of an aggregation using the ``Pythonic`` syntax is: + + +- code: | + immigration.sum((X.time[2014::2] >> 'even_years', X.time[::2] >> 'odd_years'), 'citizenship') + + +- markdown: | + Using the ``String`` syntax, the same aggregation becomes: + + +- code: | + immigration.sum('time[2014::2] >> even_years; time[::2] >> odd_years', 'citizenship') + + +- markdown: | + where we used ``;`` to separate groups of labels from the same axis. + + +# The lines below here may be deleted if you do not need them. +# --------------------------------------------------------------------------- +metadata: + kernelspec: + display_name: Python 3 + language: python + name: python3 + language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.7.3 +nbformat: 4 +nbformat_minor: 2 + diff --git a/doc/source/tutorial/tutorial_string_syntax.ipynb b/doc/source/tutorial/tutorial_string_syntax.ipynb new file mode 100644 index 000000000..dcf0b9350 --- /dev/null +++ b/doc/source/tutorial/tutorial_string_syntax.ipynb @@ -0,0 +1,276 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pythonic VS String Syntax" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import the LArray library:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from larray import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the version of LArray:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from larray import __version__\n", + "__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "LArray offers two syntaxes to build axes and make selections and aggregations.\n", + "The first one is more ``Pythonic`` (uses Python structures) \n", + "For example, you can create an *age_category* axis as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "age_category = Axis([\"0-9\", \"10-17\", \"18-66\", \"67+\"], \"age_category\")\n", + "age_category" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The second one consists of using ``strings`` that are parsed.\n", + "It is shorter to type. The same *age_category* axis could have been generated as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "age_category = Axis(\"age_category=0-9,10-17,18-66,67+\")\n", + "age_category" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " **Warning:** The drawback of the string syntax is that some characters such as `, ; = : .. [ ] >>`\n", + "have a special meaning and cannot be used with the ``String`` syntax. \n", + "If you need to work with labels containing such special characters (when importing data from an external source for example), you have to use the ``Pythonic`` syntax which allows to use any character in labels.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## String Syntax" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Axes And Arrays creation\n", + "\n", + "The string syntax allows to easily create axes.\n", + "\n", + "When creating one axis, the labels are separated using ``,``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = Axis('a=a0,a1,a2,a3')\n", + "a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The special syntax ``start..stop`` generates a sequence of labels:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = Axis('a=a0..a3')\n", + "a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When creating an array, it is possible to define several axes in the same string using ``;``" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "arr = zeros(\"a=a0..a2; b=b0,b1; c=c0..c5\")\n", + "arr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Selection\n", + "\n", + "Starting from the array: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "immigration = load_example_data('demography_eurostat').immigration\n", + "immigration.info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "an example of a selection using the ``Pythonic`` syntax is:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# since the labels 'Belgium' and 'Netherlands' also exists in the 'citizenship' axis, \n", + "# we need to explicitly specify that we want to make a selection over the 'country' axis\n", + "immigration_subset = immigration[X.country['Belgium', 'Netherlands'], 'Female', 2015:]\n", + "immigration_subset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the ``String`` syntax, the same selection becomes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "immigration_subset = immigration['country[Belgium,Netherlands]', 'Female', 2015:]\n", + "immigration_subset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aggregation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An example of an aggregation using the ``Pythonic`` syntax is:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "immigration.sum((X.time[2014::2] >> 'even_years', X.time[::2] >> 'odd_years'), 'citizenship')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the ``String`` syntax, the same aggregation becomes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "immigration.sum('time[2014::2] >> even_years; time[::2] >> odd_years', 'citizenship')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "where we used ``;`` to separate groups of labels from the same axis." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/source/tutorial/tutorial_transforming.ipyml b/doc/source/tutorial/tutorial_transforming.ipyml index a52787edd..d14adcb46 100644 --- a/doc/source/tutorial/tutorial_transforming.ipyml +++ b/doc/source/tutorial/tutorial_transforming.ipyml @@ -9,7 +9,6 @@ cells: import warnings warnings.filterwarnings("ignore", message=r'.*numpy.dtype size changed*') - id: 0 metadata: nbsphinx: hidden @@ -20,7 +19,6 @@ cells: - code: | from larray import * - id: 1 - markdown: | Check the version of LArray: @@ -30,66 +28,109 @@ cells: from larray import __version__ __version__ - id: 2 -- markdown: | - ## Manipulating axes +- code: | + # load the 'demography_eurostat' dataset + demo_eurostat = load_example_data('demography_eurostat') - code: | - # let's start with - pop = load_example_data('demography').pop[2016, 'BruCap', 90:95] + # get a copy of the 'pop' array from the 'demography_eurostat' dataset + pop = demo_eurostat.pop.copy() pop - id: 3 + +- markdown: | + ## Manipulating axes + + + LArray offers several methods to manipulate the axes and labels of an array: + + - [set_labels](../_generated/larray.LArray.set_labels.rst#larray.LArray.set_labels): to replace all or some labels of one or several axes. + - [rename](../_generated/larray.LArray.rename.rst#larray.LArray.rename): to replace one or several axis names. + - [set_axes](../_generated/larray.LArray.set_axes.rst#larray.LArray.set_axes): to replace one or several axes. + - [transpose](../_generated/larray.LArray.transpose.rst#larray.LArray.transpose): to modify the order of axes. + - [drop](../_generated/larray.LArray.drop.rst#larray.LArray.drop): to remove one or several labels. + - [combine_axes](../_generated/larray.LArray.combine_axes.rst#larray.LArray.combine_axes): to combine axes. + - [split_axes](../_generated/larray.LArray.split_axes.rst#larray.LArray.split_axes): to split one or several axes by splitting their labels and names. + - [reindex](../_generated/larray.LArray.reindex.rst#larray.LArray.reindex): to reorder, add and remove labels of one or several axes. + - [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert): to insert a label at a given position. + - markdown: | ### Relabeling - Replace all labels of one axis + Replace some labels of an axis: + + +- code: | + # replace all labels of the 'gender' axis by passing the list of all new labels + pop_new_labels = pop.set_labels('gender', ['Men', 'Women']) + pop_new_labels - code: | - # returns a copy by default - pop_new_labels = pop.set_labels('sex', ['Men', 'Women']) + # set all labels of the 'country' axis to uppercase by passing the function str.upper() + pop_new_labels = pop.set_labels('country', str.upper) pop_new_labels - id: 4 - code: | - # inplace flag avoids to create a copy - pop.set_labels('sex', ['M', 'F'], inplace=True) + # replace only one label of the 'gender' axis by passing a dict + pop_new_labels = pop.set_labels('gender', {'Male': 'Men'}) + pop_new_labels + + +- markdown: | + See [set_labels](../_generated/larray.LArray.set_labels.rst#larray.LArray.set_labels) for more details and examples. - id: 5 - markdown: | ### Renaming axes - Rename one axis + Rename one axis: - code: | - pop.info + # 'rename' returns a copy of the array + pop_new_names = pop.rename('time', 'year') + pop_new_names + + +- markdown: | + Rename several axes at once: - id: 6 - code: | - # 'rename' returns a copy of the array - pop2 = pop.rename('sex', 'gender') - pop2 + pop_new_names = pop.rename({'gender': 'sex', 'time': 'year'}) + pop_new_names + + +- markdown: | + See [rename](../_generated/larray.LArray.rename.rst#larray.LArray.rename) for more details and examples. + + +- markdown: | + ### Replacing Axes + + Replace one axis: + + +- code: | + new_gender = Axis('sex=Men,Women') + pop_new_axis = pop.set_axes('gender', new_gender) + pop_new_axis - id: 7 - markdown: | - Rename several axes at once + Replace several axes at once: - code: | - # No x. here because sex and nat are keywords and not actual axes - pop2 = pop.rename(sex='gender', nat='nationality') - pop2 + new_country = Axis('country_codes=BE,FR,DE') + pop_new_axes = pop.set_axes({'country': new_country, 'gender': new_gender}) + pop_new_axes - id: 8 - markdown: | ### Reordering axes @@ -101,75 +142,238 @@ cells: - code: | - # starting order : age, sex, nat + # starting order : country, gender, time pop - id: 9 - code: | - # no argument --> reverse axes - pop.transpose() + # no argument --> reverse all axes + pop_transposed = pop.transpose() # .T is a shortcut for .transpose() - pop.T + pop_transposed = pop.T + + pop_transposed - id: 10 - code: | # reorder according to list - pop.transpose('age', 'nat', 'sex') + pop_transposed = pop.transpose('gender', 'country', 'time') + pop_transposed + + +- code: | + # move 'time' axis at first place + # not mentioned axes come after those which are mentioned (and keep their relative order) + pop_transposed = pop.transpose('time') + pop_transposed + + +- code: | + # move 'gender' axis at last place + # not mentioned axes come before those which are mentioned (and keep their relative order) + pop_transposed = pop.transpose(..., 'gender') + pop_transposed + + +- markdown: | + See [transpose](../_generated/larray.LArray.transpose.rst#larray.LArray.transpose) for more details and examples. + + +- markdown: | + ### Dropping Labels + + +- code: | + pop_labels_dropped = pop.drop([2014, 2016]) + pop_labels_dropped + + +- markdown: | + See [drop](../_generated/larray.LArray.drop.rst#larray.LArray.drop) for more details and examples. + + +- markdown: | + ### Combine And Split Axes + + Combine two axes: - id: 11 - code: | - # axes not mentioned come after those which are mentioned (and keep their relative order) - pop.transpose('sex') + pop_combined_axes = pop.combine_axes(('country', 'gender')) + pop_combined_axes + + +- markdown: | + Split an axis: + + +- code: | + pop_split_axes = pop_combined_axes.split_axes('country_gender') + pop_split_axes + + +- markdown: | + See [combine_axes](../_generated/larray.LArray.combine_axes.rst#larray.LArray.combine_axes) and [split_axes](../_generated/larray.LArray.split_axes.rst#larray.LArray.split_axes) for more details and examples. + + +- markdown: | + ### Reordering, adding and removing labels + + The ``reindex`` method allows to reorder, add and remove labels along one axis: + + +- code: | + # reverse years + remove 2013 + add 2018 + copy data for 2017 to 2018 + pop_new_time = pop.reindex('time', '2018..2014', fill_value=pop[2017]) + pop_new_time + + +- markdown: | + or several axes: + + +- code: | + pop_new = pop.reindex({'country': 'country=Luxembourg,Belgium,France,Germany', + 'time': 'time=2018..2014'}, fill_value=0) + pop_new + + +- markdown: | + See [reindex](../_generated/larray.LArray.reindex.rst#larray.LArray.reindex) for more details and examples. + + +- markdown: | + Another way to insert new labels is to use the ``insert`` method: + + +- code: | + # insert a new country before 'France' with all values set to 0 + pop_new_country = pop.insert(0, before='France', label='Luxembourg') + # or equivalently + pop_new_country = pop.insert(0, after='Belgium', label='Luxembourg') + + pop_new_country + + +- markdown: | + See [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert) for more details and examples. + + +- markdown: | + ## Sorting + + + - [sort_axes](../_generated/larray.LArray.sort_axes.rst#larray.LArray.sort_axes): sort the labels of an axis. + - [labelsofsorted](../_generated/larray.LArray.labelsofsorted.rst#larray.LArray.labelsofsorted): give labels which would sort an axis. + - [sort_values](../_generated/larray.LArray.sort_values.rst#larray.LArray.sort_values): sort axes according to values + + +- code: | + # get a copy of the 'pop_benelux' array + pop_benelux = demo_eurostat.pop_benelux.copy() + pop_benelux + + +- markdown: | + Sort an axis (alphabetically if labels are strings) + + +- code: | + pop_sorted = pop_benelux.sort_axes('gender') + pop_sorted + + +- markdown: | + Give labels which would sort the axis + + +- code: | + pop_benelux.labelsofsorted('country') + + +- markdown: | + Sort according to values + + +- code: | + pop_sorted = pop_benelux.sort_values(('Male', 2017)) + pop_sorted - id: 12 - markdown: | ## Combining arrays - ### Append/Prepend + LArray offers several methods to combine arrays: - Append/prepend one element to an axis of an array + - [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert): inserts an array in another array along an axis + - [append](../_generated/larray.LArray.append.rst#larray.LArray.append): adds an array at the end of an axis. + - [prepend](../_generated/larray.LArray.prepend.rst#larray.LArray.prepend): adds an array at the beginning of an axis. + - [extend](../_generated/larray.LArray.extend.rst#larray.LArray.extend): extends an array along an axis. + - [stack](../_generated/larray.stack.rst#larray.stack): combines several arrays along an axis. + + +- markdown: | + ### Insert - code: | - pop = load_example_data('demography').pop[2016, 'BruCap', 90:95] + other_countries = zeros((Axis('country=Luxembourg,Netherlands'), pop.gender, pop.time), dtype=int) - # imagine that you have now acces to the number of non-EU foreigners - data = [[25, 54], [15, 33], [12, 28], [11, 37], [5, 21], [7, 19]] - pop_non_eu = LArray(data, pop['FO'].axes) + # insert new countries before 'France' + pop_new_countries = pop.insert(other_countries, before='France') + pop_new_countries + + +- markdown: | + See [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert) for more details and examples. + + +- markdown: | + ### Append - # you can do something like this - pop = pop.append('nat', pop_non_eu, 'NEU') - pop + Append one element to an axis of an array: - id: 13 - code: | - # you can also add something at the start of an axis - pop = pop.prepend('sex', pop.sum('sex'), 'B') - pop + # append data for 'Luxembourg' + pop_new = pop.append('country', pop_benelux['Luxembourg'], 'Luxembourg') + pop_new - id: 14 - markdown: | - The value being appended/prepended can have missing (or even extra) axes as long as common axes are compatible + The value being appended can have missing (or even extra) axes as long as common axes are compatible: - code: | - aliens = zeros(pop.axes['sex']) - aliens + pop_lux = LArray([-1, 1], pop.gender) + pop_lux - id: 15 - code: | - pop = pop.append('nat', aliens, 'AL') - pop + pop_new = pop.append('country', pop_lux, 'Luxembourg') + pop_new + + +- markdown: | + See [append](../_generated/larray.LArray.append.rst#larray.LArray.append) for more details and examples. + + +- markdown: | + ### Prepend + + Prepend one element to an axis of an array: + + +- code: | + # append data for 'Luxembourg' + pop_new = pop.prepend('country', pop_benelux['Luxembourg'], 'Luxembourg') + pop_new + + +- markdown: | + See [prepend](../_generated/larray.LArray.prepend.rst#larray.LArray.prepend) for more details and examples. - id: 16 - markdown: | ### Extend @@ -178,14 +382,13 @@ cells: - code: | - _pop = load_example_data('demography').pop - pop = _pop[2016, 'BruCap', 90:95] - pop_next = _pop[2016, 'BruCap', 96:100] - - # concatenate along age axis - pop.extend('age', pop_next) + pop_extended = pop.extend('country', pop_benelux[['Luxembourg', 'Netherlands']]) + pop_extended + + +- markdown: | + See [extend](../_generated/larray.LArray.extend.rst#larray.LArray.extend) for more details and examples. - id: 17 - markdown: | ### Stack @@ -194,49 +397,62 @@ cells: - code: | - # imagine you have loaded data for each nationality in different arrays (e.g. loaded from different Excel sheets) - pop_be, pop_fo = pop['BE'], pop['FO'] - - # first way to stack them - nat = Axis('nat=BE,FO,NEU') - pop = stack([pop_be, pop_fo, pop_non_eu], nat) + # imagine you have loaded data for each country in different arrays + # (e.g. loaded from different Excel sheets) + pop_be = pop['Belgium'] + pop_fr = pop['France'] + pop_de = pop['Germany'] - # second way - pop = stack([('BE', pop_be), ('FO', pop_fo), ('NEU', pop_non_eu)], 'nat') - - pop + pop_stacked = stack({'Belgium': pop_be, 'France': pop_fr, 'Germany': pop_de}, 'country') + pop_stacked - id: 18 - markdown: | - ## Sorting + See [stack](../_generated/larray.stack.rst#larray.stack) for more details and examples. + + +- markdown: | + ## Aligning Arrays - Sort an axis (alphabetically if labels are strings) + The ``align`` method align two arrays on their axes with a specified join method. + In other words, it ensure all common axes are compatible. - code: | - pop_sorted = pop.sort_axes('nat') - pop_sorted + # get a copy of the 'births' array + births = demo_eurostat.births.copy() + + # align the two arrays with the 'inner' join method + pop_aligned, births_aligned = pop_benelux.align(births, join='inner') - id: 19 -- markdown: | - Give labels which would sort the axis +- code: | + print('pop_benelux before align:') + print(pop_benelux) + print() + print('pop_benelux after align:') + print(pop_aligned) - code: | - pop_sorted.labelsofsorted('sex') + print('births before align:') + print(births) + print() + print('births after align:') + print(births_aligned) - id: 20 - markdown: | - Sort according to values + Aligned arrays can then be used in arithmetic operations: - code: | - pop_sorted.sort_values((90, 'F')) + pop_aligned - births_aligned + + +- markdown: | + See [align](../_generated/larray.LArray.align.rst#larray.LArray.align) for more details and examples. - id: 21 # The lines below here may be deleted if you do not need them. # --------------------------------------------------------------------------- @@ -255,22 +471,10 @@ metadata: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.4 + version: 3.7.3 livereveal: autolaunch: false scroll: true nbformat: 4 nbformat_minor: 2 -# --------------------------------------------------------------------------- -data: - [{execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, - outputs: []}, {execution_count: null, outputs: []}, {execution_count: null, outputs: []}, - {execution_count: null, outputs: []}, {execution_count: null, outputs: []}] - diff --git a/doc/source/tutorial/tutorial_transforming.ipynb b/doc/source/tutorial/tutorial_transforming.ipynb index 7f1357185..1632f2d78 100644 --- a/doc/source/tutorial/tutorial_transforming.ipynb +++ b/doc/source/tutorial/tutorial_transforming.ipynb @@ -54,10 +54,13 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Manipulating axes\n" + "# load the 'demography_eurostat' dataset\n", + "demo_eurostat = load_example_data('demography_eurostat')" ] }, { @@ -66,18 +69,38 @@ "metadata": {}, "outputs": [], "source": [ - "# let's start with\n", - "pop = load_example_data('demography').pop[2016, 'BruCap', 90:95]\n", + "# get a copy of the 'pop' array from the 'demography_eurostat' dataset\n", + "pop = demo_eurostat.pop.copy()\n", "pop" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Manipulating axes\n", + "\n", + "\n", + "LArray offers several methods to manipulate the axes and labels of an array:\n", + "\n", + "- [set_labels](../_generated/larray.LArray.set_labels.rst#larray.LArray.set_labels): to replace all or some labels of one or several axes.\n", + "- [rename](../_generated/larray.LArray.rename.rst#larray.LArray.rename): to replace one or several axis names.\n", + "- [set_axes](../_generated/larray.LArray.set_axes.rst#larray.LArray.set_axes): to replace one or several axes.\n", + "- [transpose](../_generated/larray.LArray.transpose.rst#larray.LArray.transpose): to modify the order of axes.\n", + "- [drop](../_generated/larray.LArray.drop.rst#larray.LArray.drop): to remove one or several labels.\n", + "- [combine_axes](../_generated/larray.LArray.combine_axes.rst#larray.LArray.combine_axes): to combine axes.\n", + "- [split_axes](../_generated/larray.LArray.split_axes.rst#larray.LArray.split_axes): to split one or several axes by splitting their labels and names.\n", + "- [reindex](../_generated/larray.LArray.reindex.rst#larray.LArray.reindex): to reorder, add and remove labels of one or several axes.\n", + "- [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert): to insert a label at a given position.\n" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Relabeling\n", "\n", - "Replace all labels of one axis\n" + "Replace some labels of an axis:\n" ] }, { @@ -86,8 +109,8 @@ "metadata": {}, "outputs": [], "source": [ - "# returns a copy by default\n", - "pop_new_labels = pop.set_labels('sex', ['Men', 'Women'])\n", + "# replace all labels of the 'gender' axis by passing the list of all new labels\n", + "pop_new_labels = pop.set_labels('gender', ['Men', 'Women'])\n", "pop_new_labels" ] }, @@ -97,8 +120,27 @@ "metadata": {}, "outputs": [], "source": [ - "# inplace flag avoids to create a copy\n", - "pop.set_labels('sex', ['M', 'F'], inplace=True)" + "# set all labels of the 'country' axis to uppercase by passing the function str.upper()\n", + "pop_new_labels = pop.set_labels('country', str.upper)\n", + "pop_new_labels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# replace only one label of the 'gender' axis by passing a dict\n", + "pop_new_labels = pop.set_labels('gender', {'Male': 'Men'})\n", + "pop_new_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [set_labels](../_generated/larray.LArray.set_labels.rst#larray.LArray.set_labels) for more details and examples." ] }, { @@ -107,7 +149,7 @@ "source": [ "### Renaming axes\n", "\n", - "Rename one axis\n" + "Rename one axis:\n" ] }, { @@ -116,7 +158,16 @@ "metadata": {}, "outputs": [], "source": [ - "pop.info" + "# 'rename' returns a copy of the array\n", + "pop_new_names = pop.rename('time', 'year')\n", + "pop_new_names" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rename several axes at once:\n" ] }, { @@ -125,16 +176,24 @@ "metadata": {}, "outputs": [], "source": [ - "# 'rename' returns a copy of the array\n", - "pop2 = pop.rename('sex', 'gender')\n", - "pop2" + "pop_new_names = pop.rename({'gender': 'sex', 'time': 'year'})\n", + "pop_new_names" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [rename](../_generated/larray.LArray.rename.rst#larray.LArray.rename) for more details and examples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Rename several axes at once\n" + "### Replacing Axes\n", + "\n", + "Replace one axis:" ] }, { @@ -143,9 +202,27 @@ "metadata": {}, "outputs": [], "source": [ - "# No x. here because sex and nat are keywords and not actual axes\n", - "pop2 = pop.rename(sex='gender', nat='nationality')\n", - "pop2" + "new_gender = Axis('sex=Men,Women')\n", + "pop_new_axis = pop.set_axes('gender', new_gender)\n", + "pop_new_axis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Replace several axes at once:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_country = Axis('country_codes=BE,FR,DE') \n", + "pop_new_axes = pop.set_axes({'country': new_country, 'gender': new_gender})\n", + "pop_new_axes" ] }, { @@ -166,7 +243,7 @@ "metadata": {}, "outputs": [], "source": [ - "# starting order : age, sex, nat\n", + "# starting order : country, gender, time\n", "pop" ] }, @@ -176,11 +253,13 @@ "metadata": {}, "outputs": [], "source": [ - "# no argument --> reverse axes\n", - "pop.transpose()\n", + "# no argument --> reverse all axes\n", + "pop_transposed = pop.transpose()\n", "\n", "# .T is a shortcut for .transpose()\n", - "pop.T" + "pop_transposed = pop.T\n", + "\n", + "pop_transposed" ] }, { @@ -190,7 +269,209 @@ "outputs": [], "source": [ "# reorder according to list\n", - "pop.transpose('age', 'nat', 'sex')" + "pop_transposed = pop.transpose('gender', 'country', 'time')\n", + "pop_transposed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# move 'time' axis at first place\n", + "# not mentioned axes come after those which are mentioned (and keep their relative order)\n", + "pop_transposed = pop.transpose('time')\n", + "pop_transposed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# move 'gender' axis at last place\n", + "# not mentioned axes come before those which are mentioned (and keep their relative order)\n", + "pop_transposed = pop.transpose(..., 'gender')\n", + "pop_transposed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [transpose](../_generated/larray.LArray.transpose.rst#larray.LArray.transpose) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dropping Labels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_labels_dropped = pop.drop([2014, 2016])\n", + "pop_labels_dropped" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [drop](../_generated/larray.LArray.drop.rst#larray.LArray.drop) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Combine And Split Axes\n", + "\n", + "Combine two axes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_combined_axes = pop.combine_axes(('country', 'gender'))\n", + "pop_combined_axes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Split an axis:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_split_axes = pop_combined_axes.split_axes('country_gender')\n", + "pop_split_axes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [combine_axes](../_generated/larray.LArray.combine_axes.rst#larray.LArray.combine_axes) and [split_axes](../_generated/larray.LArray.split_axes.rst#larray.LArray.split_axes) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Reordering, adding and removing labels\n", + "\n", + "The ``reindex`` method allows to reorder, add and remove labels along one axis:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# reverse years + remove 2013 + add 2018 + copy data for 2017 to 2018\n", + "pop_new_time = pop.reindex('time', '2018..2014', fill_value=pop[2017])\n", + "pop_new_time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or several axes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_new = pop.reindex({'country': 'country=Luxembourg,Belgium,France,Germany', \n", + " 'time': 'time=2018..2014'}, fill_value=0)\n", + "pop_new" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [reindex](../_generated/larray.LArray.reindex.rst#larray.LArray.reindex) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another way to insert new labels is to use the ``insert`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# insert a new country before 'France' with all values set to 0\n", + "pop_new_country = pop.insert(0, before='France', label='Luxembourg')\n", + "# or equivalently\n", + "pop_new_country = pop.insert(0, after='Belgium', label='Luxembourg')\n", + "\n", + "pop_new_country" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sorting\n", + "\n", + "\n", + "- [sort_axes](../_generated/larray.LArray.sort_axes.rst#larray.LArray.sort_axes): sort the labels of an axis.\n", + "- [labelsofsorted](../_generated/larray.LArray.labelsofsorted.rst#larray.LArray.labelsofsorted): give labels which would sort an axis. \n", + "- [sort_values](../_generated/larray.LArray.sort_values.rst#larray.LArray.sort_values): sort axes according to values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get a copy of the 'pop_benelux' array\n", + "pop_benelux = demo_eurostat.pop_benelux.copy()\n", + "pop_benelux" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sort an axis (alphabetically if labels are strings)" ] }, { @@ -199,8 +480,41 @@ "metadata": {}, "outputs": [], "source": [ - "# axes not mentioned come after those which are mentioned (and keep their relative order)\n", - "pop.transpose('sex')" + "pop_sorted = pop_benelux.sort_axes('gender')\n", + "pop_sorted" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Give labels which would sort the axis\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_benelux.labelsofsorted('country')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sort according to values\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pop_sorted = pop_benelux.sort_values(('Male', 2017))\n", + "pop_sorted" ] }, { @@ -209,9 +523,20 @@ "source": [ "## Combining arrays\n", "\n", - "### Append/Prepend\n", + "LArray offers several methods to combine arrays:\n", "\n", - "Append/prepend one element to an axis of an array\n" + "- [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert): inserts an array in another array along an axis\n", + "- [append](../_generated/larray.LArray.append.rst#larray.LArray.append): adds an array at the end of an axis.\n", + "- [prepend](../_generated/larray.LArray.prepend.rst#larray.LArray.prepend): adds an array at the beginning of an axis.\n", + "- [extend](../_generated/larray.LArray.extend.rst#larray.LArray.extend): extends an array along an axis.\n", + "- [stack](../_generated/larray.stack.rst#larray.stack): combines several arrays along an axis.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Insert" ] }, { @@ -220,15 +545,27 @@ "metadata": {}, "outputs": [], "source": [ - "pop = load_example_data('demography').pop[2016, 'BruCap', 90:95]\n", + "other_countries = zeros((Axis('country=Luxembourg,Netherlands'), pop.gender, pop.time), dtype=int)\n", "\n", - "# imagine that you have now acces to the number of non-EU foreigners\n", - "data = [[25, 54], [15, 33], [12, 28], [11, 37], [5, 21], [7, 19]]\n", - "pop_non_eu = LArray(data, pop['FO'].axes)\n", + "# insert new countries before 'France'\n", + "pop_new_countries = pop.insert(other_countries, before='France')\n", + "pop_new_countries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [insert](../_generated/larray.LArray.insert.rst#larray.LArray.insert) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Append\n", "\n", - "# you can do something like this\n", - "pop = pop.append('nat', pop_non_eu, 'NEU')\n", - "pop" + "Append one element to an axis of an array:" ] }, { @@ -237,16 +574,16 @@ "metadata": {}, "outputs": [], "source": [ - "# you can also add something at the start of an axis\n", - "pop = pop.prepend('sex', pop.sum('sex'), 'B')\n", - "pop" + "# append data for 'Luxembourg'\n", + "pop_new = pop.append('country', pop_benelux['Luxembourg'], 'Luxembourg')\n", + "pop_new" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The value being appended/prepended can have missing (or even extra) axes as long as common axes are compatible\n" + "The value being appended can have missing (or even extra) axes as long as common axes are compatible:\n" ] }, { @@ -255,8 +592,8 @@ "metadata": {}, "outputs": [], "source": [ - "aliens = zeros(pop.axes['sex'])\n", - "aliens" + "pop_lux = LArray([-1, 1], pop.gender)\n", + "pop_lux" ] }, { @@ -265,8 +602,42 @@ "metadata": {}, "outputs": [], "source": [ - "pop = pop.append('nat', aliens, 'AL')\n", - "pop" + "pop_new = pop.append('country', pop_lux, 'Luxembourg')\n", + "pop_new" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [append](../_generated/larray.LArray.append.rst#larray.LArray.append) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepend\n", + "\n", + "Prepend one element to an axis of an array:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# append data for 'Luxembourg'\n", + "pop_new = pop.prepend('country', pop_benelux['Luxembourg'], 'Luxembourg')\n", + "pop_new" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [prepend](../_generated/larray.LArray.prepend.rst#larray.LArray.prepend) for more details and examples." ] }, { @@ -284,12 +655,15 @@ "metadata": {}, "outputs": [], "source": [ - "_pop = load_example_data('demography').pop\n", - "pop = _pop[2016, 'BruCap', 90:95]\n", - "pop_next = _pop[2016, 'BruCap', 96:100]\n", - "\n", - "# concatenate along age axis\n", - "pop.extend('age', pop_next)" + "pop_extended = pop.extend('country', pop_benelux[['Luxembourg', 'Netherlands']])\n", + "pop_extended" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [extend](../_generated/larray.LArray.extend.rst#larray.LArray.extend) for more details and examples." ] }, { @@ -307,26 +681,31 @@ "metadata": {}, "outputs": [], "source": [ - "# imagine you have loaded data for each nationality in different arrays (e.g. loaded from different Excel sheets)\n", - "pop_be, pop_fo = pop['BE'], pop['FO']\n", - "\n", - "# first way to stack them\n", - "nat = Axis('nat=BE,FO,NEU')\n", - "pop = stack([pop_be, pop_fo, pop_non_eu], nat)\n", - "\n", - "# second way\n", - "pop = stack([('BE', pop_be), ('FO', pop_fo), ('NEU', pop_non_eu)], 'nat')\n", + "# imagine you have loaded data for each country in different arrays \n", + "# (e.g. loaded from different Excel sheets)\n", + "pop_be = pop['Belgium']\n", + "pop_fr = pop['France']\n", + "pop_de = pop['Germany']\n", "\n", - "pop" + "pop_stacked = stack({'Belgium': pop_be, 'France': pop_fr, 'Germany': pop_de}, 'country')\n", + "pop_stacked" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Sorting\n", + "See [stack](../_generated/larray.stack.rst#larray.stack) for more details and examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aligning Arrays\n", "\n", - "Sort an axis (alphabetically if labels are strings)\n" + "The ``align`` method align two arrays on their axes with a specified join method.\n", + "In other words, it ensure all common axes are compatible." ] }, { @@ -335,15 +714,24 @@ "metadata": {}, "outputs": [], "source": [ - "pop_sorted = pop.sort_axes('nat')\n", - "pop_sorted" + "# get a copy of the 'births' array\n", + "births = demo_eurostat.births.copy()\n", + "\n", + "# align the two arrays with the 'inner' join method\n", + "pop_aligned, births_aligned = pop_benelux.align(births, join='inner')" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Give labels which would sort the axis\n" + "print('pop_benelux before align:')\n", + "print(pop_benelux)\n", + "print()\n", + "print('pop_benelux after align:')\n", + "print(pop_aligned)" ] }, { @@ -352,14 +740,18 @@ "metadata": {}, "outputs": [], "source": [ - "pop_sorted.labelsofsorted('sex')" + "print('births before align:')\n", + "print(births)\n", + "print()\n", + "print('births after align:')\n", + "print(births_aligned)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Sort according to values\n" + "Aligned arrays can then be used in arithmetic operations:" ] }, { @@ -368,7 +760,14 @@ "metadata": {}, "outputs": [], "source": [ - "pop_sorted.sort_values((90, 'F'))" + "pop_aligned - births_aligned" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [align](../_generated/larray.LArray.align.rst#larray.LArray.align) for more details and examples." ] } ], @@ -389,7 +788,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.3" }, "livereveal": { "autolaunch": false, diff --git a/larray/example.py b/larray/example.py index d9f9208d5..578757b54 100644 --- a/larray/example.py +++ b/larray/example.py @@ -5,10 +5,9 @@ _TEST_DIR = os.path.join(os.path.dirname(__file__), 'tests') EXAMPLE_FILES_DIR = os.path.join(_TEST_DIR, 'data') -# TODO : replace 'demography.h5' by 'population_session.h5' and remove 'demo' ? AVAILABLE_EXAMPLE_DATA = { - 'demo': os.path.join(EXAMPLE_FILES_DIR, 'population_session.h5'), - 'demography': os.path.join(EXAMPLE_FILES_DIR, 'demography.h5') + 'demography': os.path.join(EXAMPLE_FILES_DIR, 'demography.h5'), + 'demography_eurostat': os.path.join(EXAMPLE_FILES_DIR, 'demography_eurostat.h5') } AVAILABLE_EXAMPLE_FILES = os.listdir(EXAMPLE_FILES_DIR) @@ -43,6 +42,7 @@ def get_example_filepath(fname): return fpath +# TODO : replace # doctest: +SKIP by # doctest: +NORMALIZE_WHITESPACE once Python 2 has been dropped def load_example_data(name): r"""Load arrays used in the tutorial so that all examples in it can be reproduced. @@ -52,29 +52,37 @@ def load_example_data(name): Example data to load. Available example datasets are: - demography + - demography_eurostat Returns ------- Session - Session containing one or several arrays + Session containing one or several arrays. Examples -------- >>> demo = load_example_data('demography') - >>> demo.pop.info # doctest: +SKIP - 26 x 3 x 121 x 2 x 2 - time [26]: 1991 1992 1993 ... 2014 2015 2016 - geo [3]: 'BruCap' 'Fla' 'Wal' - age [121]: 0 1 2 ... 118 119 120 - sex [2]: 'M' 'F' - nat [2]: 'BE' 'FO' - >>> demo.qx.info # doctest: +SKIP - 26 x 3 x 121 x 2 x 2 - time [26]: 1991 1992 1993 ... 2014 2015 2016 - geo [3]: 'BruCap' 'Fla' 'Wal' - age [121]: 0 1 2 ... 118 119 120 - sex [2]: 'M' 'F' - nat [2]: 'BE' 'FO' + >>> print(demo.summary()) # doctest: +NORMALIZE_WHITESPACE + hh: time, geo, hh_type (26 x 3 x 7) [int64] + pop: time, geo, age, sex, nat (26 x 3 x 121 x 2 x 2) [int64] + qx: time, geo, age, sex, nat (26 x 3 x 121 x 2 x 2) [float64] + >>> demo = load_example_data('demography_eurostat') # doctest: +SKIP + >>> print(demo.summary()) # doctest: +SKIP + Metadata: + title: Demographic datasets for a small selection of countries in Europe + source: demo_jpan, demo_fasec, demo_magec and migr_imm1ctz tables from Eurostat + gender: gender ['Male' 'Female'] (2) + country: country ['Belgium' 'France' 'Germany'] (3) + country_benelux: country_benelux ['Belgium' 'Luxembourg' 'Netherlands'] (3) + citizenship: citizenship ['Belgium' 'Luxembourg' 'Netherlands'] (3) + time: time [2013 2014 2015 2016 2017] (5) + even_years: time[2014 2016] >> even_years (2) + odd_years: time[2013 2015 2017] >> odd_years (3) + births: country, gender, time (3 x 2 x 5) [int32] + deaths: country, gender, time (3 x 2 x 5) [int32] + immigration: country, citizenship, gender, time (3 x 3 x 2 x 5) [int32] + pop: country, gender, time (3 x 2 x 5) [int32] + pop_benelux: country, gender, time (3 x 2 x 5) [int32] """ if name is None: name = 'demography' diff --git a/larray/inout/csv.py b/larray/inout/csv.py index 440771afe..87318f15a 100644 --- a/larray/inout/csv.py +++ b/larray/inout/csv.py @@ -78,8 +78,8 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse country,gender\time,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 - France,Male,31772665,31936596,32175328 - France,Female,33827685,34005671,34280951 + France,Male,31772665,32045129,32174258 + France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 Germany,Female,41142770,41210540,41362080 @@ -93,8 +93,8 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse country gender\time 2013 2014 2015 Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 - France Male 31772665 31936596 32175328 - France Female 33827685 34005671 34280951 + France Male 31772665 32045129 32174258 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 41142770 41210540 41362080 @@ -108,7 +108,7 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse country,gender\time,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 - France,Female,33827685,34005671,34280951 + France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 >>> # by default, cells associated with missing label combinations are filled with NaN. >>> # In that case, an int array is converted to a float array. @@ -117,7 +117,7 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse Belgium Male 5472856.0 5493792.0 5524068.0 Belgium Female 5665118.0 5687048.0 5713206.0 France Male nan nan nan - France Female 33827685.0 34005671.0 34280951.0 + France Female 33827685.0 34120851.0 34283895.0 Germany Male 39380976.0 39556923.0 39835457.0 Germany Female nan nan nan >>> # using argument 'fill_value', you can choose which value to use to fill missing cells. @@ -126,7 +126,7 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 France Male 0 0 0 - France Female 33827685 34005671 34280951 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 0 0 0 @@ -140,8 +140,8 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse country,gender,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 - France,Male,31772665,31936596,32175328 - France,Female,33827685,34005671,34280951 + France,Male,31772665,32045129,32174258 + France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 Germany,Female,41142770,41210540,41362080 >>> # read the array stored in the CSV file as is @@ -177,13 +177,13 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse Belgium,2014,11180840 Belgium,2015,11237274 France,2013,65600350 - France,2014,65942267 - France,2015,66456279 + France,2014,66165980 + France,2015,66458153 >>> # to read arrays stored in 'narrow' format, you must pass wide=False to read_csv >>> read_csv(fname, wide=False) country\time 2013 2014 2015 Belgium 11137974 11180840 11237274 - France 65600350 65942267 66456279 + France 65600350 66165980 66458153 """ if not np.isnan(na): fill_value = na diff --git a/larray/inout/excel.py b/larray/inout/excel.py index d7d7cfff8..4e76bb1c6 100644 --- a/larray/inout/excel.py +++ b/larray/inout/excel.py @@ -84,8 +84,8 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, country gender\time 2013 2014 2015 Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 - France Male 31772665 31936596 32175328 - France Female 33827685 34005671 34280951 + France Male 31772665 32045129 32174258 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 41142770 41210540 41362080 @@ -109,7 +109,7 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, country gender\time 2013 2014 2015 Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 - France Female 33827685 34005671 34280951 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 By default, cells associated with missing label combinations are filled with NaN. In that case, an int array @@ -120,7 +120,7 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, Belgium Male 5472856.0 5493792.0 5524068.0 Belgium Female 5665118.0 5687048.0 5713206.0 France Male nan nan nan - France Female 33827685.0 34005671.0 34280951.0 + France Female 33827685.0 34120851.0 34283895.0 Germany Male 39380976.0 39556923.0 39835457.0 Germany Female nan nan nan @@ -131,7 +131,7 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 France Male 0 0 0 - France Female 33827685 34005671 34280951 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 0 0 0 @@ -142,8 +142,8 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, country gender 2013 2014 2015 Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 - France Male 31772665 31936596 32175328 - France Female 33827685 34005671 34280951 + France Male 31772665 32045129 32174258 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 41142770 41210540 41362080 @@ -177,14 +177,14 @@ def read_excel(filepath, sheet=0, nb_axes=None, index_col=None, fill_value=nan, Belgium 2014 11180840 Belgium 2015 11237274 France 2013 65600350 - France 2014 65942267 - France 2015 66456279 + France 2014 66165980 + France 2015 66458153 >>> # to read arrays stored in 'narrow' format, you must pass wide=False to read_excel >>> read_excel(fname, 'pop_narrow_format', wide=False) country\time 2013 2014 2015 Belgium 11137974 11180840 11237274 - France 65600350 65942267 66456279 + France 65600350 66165980 66458153 Extract array from a given range (xlwings only) diff --git a/larray/inout/hdf.py b/larray/inout/hdf.py index 92bbc7516..25d0df0eb 100644 --- a/larray/inout/hdf.py +++ b/larray/inout/hdf.py @@ -57,8 +57,8 @@ def read_hdf(filepath_or_buffer, key, fill_value=nan, na=nan, sort_rows=False, s country gender\time 2013 2014 2015 Belgium Male 5472856 5493792 5524068 Belgium Female 5665118 5687048 5713206 - France Male 31772665 31936596 32175328 - France Female 33827685 34005671 34280951 + France Male 31772665 32045129 32174258 + France Female 33827685 34120851 34283895 Germany Male 39380976 39556923 39835457 Germany Female 41142770 41210540 41362080 """ diff --git a/larray/inout/xw_reporting.py b/larray/inout/xw_reporting.py index 243c6048c..424cc124d 100644 --- a/larray/inout/xw_reporting.py +++ b/larray/inout/xw_reporting.py @@ -79,7 +79,7 @@ def template(self): Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') Passing the name of the template (only if a template directory has been set) @@ -245,7 +245,7 @@ def add_graph(self, data, title=None, template=None, width=None, height=None): Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') >>> report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) >>> sheet_be = report.new_sheet('Belgium') @@ -300,7 +300,7 @@ def add_graphs(self, array_per_title, axis_per_loop_variable, template=None, wid Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') >>> report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) >>> sheet_pop = report.new_sheet('Population') @@ -353,7 +353,7 @@ class AbstractExcelReport(AbstractReportItem): Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') >>> report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) Set a new destination sheet @@ -428,7 +428,7 @@ def new_sheet(self, sheet_name): Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') >>> report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) >>> # prepare new output sheet named 'Belgium' @@ -471,7 +471,7 @@ def to_excel(self, filepath, data_sheet_name='__data__', overwrite=True): Examples -------- - >>> demo = load_example_data('demo') + >>> demo = load_example_data('demography_eurostat') >>> report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) >>> report.template = 'Line_Marker' diff --git a/larray/tests/data/births_and_deaths.xlsx b/larray/tests/data/births_and_deaths.xlsx index 507815dc6e7bc38fc6c2964066b0fefcdede0c4c..4751568471ee3ef98b45bf5b3e01dcfa2718c0ac 100644 GIT binary patch delta 3039 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 delta 3024 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 literal 0 HcmV?d00001 diff --git a/larray/tests/data/demography_eurostat.xlsx b/larray/tests/data/demography_eurostat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d7da56f1d7319297f7b0fb0ceba35fc8091c5acd GIT binary patch literal 15834 zcmeHu^Lu4owsmaVwpDQ|wr$(CDzIBtE<0%;NJbi+0S{- zp5vUe_ZVxAHP>8nlE5G+0AK(R0001l0H8K!1KxlD0Ae3+kpUopGzD#KoQ!Rpbd}ug zj2*S<+^nq#@f?6UHAyE4u|= zbrT@MS-Hl5^4$n?FD+M~0#W!HhPvW-F+#I%!mguCrQzQ{bC~Fy02je$Dw6DxT>o(7 z$gPLykyj)qA~-t##frPF14Cv3jUnG6ijumc`61I#g^!eh5sf8lcTl-+wj#rxy1q3P z@WPqXI~((i|7CFj%ZkM-P>UupCR?SUlh8glRv11bqEU4;u(5nRkX)hb$90HghqG8I z(`s*igPSl$Bpc@s@@{Oa6P>b8)rknwfj9K|L@-?nud^*rA&fV>wPL_UBR5RI=JmqD zxMEYB2uRQXx@9Dl;-nItJD)f4>4@PaAI1sp9zeHiiw#;RK;U{NBMPr`~<#KhYdmLlN2%b|M32}zmE-0oa;4Zaa5Wv5`gfXL0KH=bO z1Zg+S&DD_-+I0nbw|oj3SdUY<*=-Wc8bdM)V1s`LETI1&-1|E)fZV^Swo#dZ`1a!y zsSi;?f2da1!Pv@?p6;*L|5NM#Vu}9CU$2apmhESN3BC|}4HZa>PFZ zzjXa0f&HgQA`dimCV&9|q#*$S;6Lt+n-#sQt%Ie3t*zx>X{}Pl+O~il;mv2}C-{IX z)IRN(^hzjkr=)us<(ieBNR$pekX=CgRY~MzGnt8MT6O74cJ;;`14zkUrs3(xnPHg->S)E*>Rsvv8b?_{p}qrPD>mj5 z`!v=P3L_H`|VZb zpY?1_hS1mR)xKnG9&VS%7iU5?C8_EypYrKG#?T<=B_QC?8D26eVr05MK z*+fo`-BGp^jAmz1Fa_uA$U5~DZHc+GT^tL~x4X`*6nqiVR6^p1!4^c)(HBor8Q;@i zpvybfI`0zCtk>cq`({HyE~nN*e~BuRDdI+aUOV0xjIPKNrm_Lj(%mQA)V6a~KgXks}LDCm?8 zsGl0`;p67+a2#TFgEF=IVNj(B4~83U$W#uo=KHQ-*dVNl?Fe7OxSG+|jTm(YvZ(;? zc17NY`e#{_OMS;sM{yjVdhDM;-T_w=tRq2DEXUxH?V@`Jbr3Y+_?%MA%v&fLBH8b? z`!ncqZTm+pbT&Vk{vOd%<=SuMn2r3|ggG#6m67^M}zKXW4Xt{xdg4myP?dU$&`)k(p~F(IUKpq+8K}|6 z=b6XnK@;49#kCmfOj8#@6Fh>&vnFUPVCQG}u54h7K-ar|RG#H8xiy*WGz90{wifbp zHJ~IZj=#=VKUq$iRBAI!O7^KOgV}vGpm}nW!4H|77r+0=BL7J;1V&6K&<_JP3IYIt z@xh*d<`+jZV`C>r`d=r;zY&NPG(Vp(mhee`q51OtIR>x_ zgGiHOS+Vy6Zh$~RF}yq}&6C6HOAhu#_C&p)XmymFceEgHPw0b`a=z`y2MhC_1H)>| zstt-flULMik7bX~q+FDpY_tbxfO%{jHdVL^0x*`Xg@dZV7h8oY8Vb;oYY5C{G8I?x zr(!4|@IngHJ7KZMo*3oTpgY*#;Nl-#{LR+oq2Dr8VD<$PPVq7eJ$x~^i+2G+vkn8j zaq}?5le<_tXPg%JR}UD9VX{7XAx3@^Hd*bKkM}n>R^CA+cX#Tyb!dq@KC}$kjc6AQ z)sx$<8j9B?nXnC88)Y^^wsJ!!jCItJw~0?ZA+5>*k`GYjwTkmp?&^(~CU#;5n*kbC ziJdfBeg~?hj~~wCKdD3Gs6MazK`KY6|4JREU(`v~w%uk&3BFKWen;5DGvIm?Ly${? zWN@sTv1nv|FlRtW8i-5qG5_@LEsKQHS4K50p+w{@qw{j-Zq3~7Nh`DdwaM9LG)T5& z;K3;^OsC!_1ZzUu}zXac&)bYkAfX1JwTA}j zq9g>UwQITDCtjev`C5@KkU%Aj9QzQHy=r>#hM|-oH)&KQxRE0F9@_eHZ0t>K%_o`O z`D|^>E~z~AI;`&PqW)4LkmP*3_lb#{do~Uxa%2h_W7fx#GEn&W!&KPr_pI;Yb44vCN zcBkC$WtO7l`Z||7VtF6sV#^@VZy5{oMK|CMV2U9FFqg3jSS~;b01vzXA#f#1;7ac{ ze}T5oVw}h*@lLWIH3B4EZ(9wgJe9EBlnjF$$=n7ffib}>ttncVQ4#$+^+xQ{inriZ zNY@aJNLT-QbE3Zj%lkl`7-IC|^Ee7DCjUvJedMfI?y41TLS%%bK(*J+Vv zl3^(J1lJCsa=4#+;yJTXAoS=|d=+kCIJ2MUIl&H9q}>cIj5$ih*r--(h~VaZwcypz z`4B?0_U(CpC?%%;gXu6Oaa=CNC0sp4q(R3JE{vfBguf;@M`-l5)1HpcAPg_@v5T)ha!}9+5o>u*d|0ZE(|FNaBozAf&!sD+vc{FEgn09-b#qu#zV7_Z>#bV z|9jH1pw9fd_(eN5!uaA_ILYjMYT2@J1@#?n{;HtWP#rRe(pd16j#7Df)5E$R zfd-PO%za_`#rjAuVJcBw5+9M*&c+*$q3nK+&P~A$jdN0&!PYjV1Fm|ZMF#3i(94Y| z6Qp)Yxo{n-sPb^o3mNsp?DbB_Q^ZWn9RKJtU{JnBOGq1?X|`$+cv^%-xr92v5@l8d z7T(=@g#_)lCfr1P9)E-zhd5N55{B7kv(hy>N#n|y5-l`m^87vdAGL4Cu}MktW3!sO z4te-FqvGY)NF;dmhAQ2S`PB&}U+G^2%&+V~HfmDCf4I+G5?3U>28<`krH8g<#nvIhkF5*7q}O z;GsfK_~*zo+z@hGmn@kp$T#G1Ri9b`NZqfyG3}YQ%)@p&qtgz}b{Cgc{o1x1wkHRw zWV+?kwk4E9;6-@Ym!QS`(xtA584Y`n(p;LY$g)Gv?H*4wUw`HWFT?A_Nhh1GG^?pl zCVmJ~oIwgJLWAZ&qmwti`ix3 z+@8yy;4~+|4d5215?oI@0j4M2kQv~!nER$Z15}V*yF3J5WcKZ{v|5z987Ar4yqrv5 zkA|L=+U`xLnu{kILU?LX8pa?|U#0aj2AW7<4$GvM;P>@&Z2y{PysyL8XRckC&;W4fI5@H9*FZcW912_AS#hJbvL=Vg6SonoQG01G&@LZ%ofDA-)O!kW*sl_G1FGH z>`#a8{k+IifHAs()yxP}4zA6yXs5Z6Z3WR~;{|hObTh%eYBtbp%hFGTMfd`Aq0Z;# zW?z6gXlG=LaXVPt8VJJ}ewwg$*IVtKWI*2y)j1hH$}y_FveOfCo=%X?nys|t^ck-4 zHTPI!Z~q9O#un)rGrcdXNwJ(2Ub+TX2O`Dyk8^gp>~1K*4-K;YcMY=qQG?OAAeHPv z_xTFSjhzo*z|-;U4)x^9-VZX-AhvOfza;4UYC4!Eb)hqpa8oE1cWAZ0&FFxe^vxs1 zC?xIW#FPc8S}~DnWh7zZ$GEq0kSE*wtNC-~cR_olawk4QYLoLinwzi#)GPU-dW;h- zW4`<4!umi-$II|liq;DBBJvml6t^-dre^unR85v!pPT`Us+}PvV`E~2AL7Zi@7t-n z!X?Rr&BH}A+}!&GY9$}CJix!?3~m~P);Im zUudK=vM!0POMt7D$t`R!!d z5SkVvYONXqh?f+-HDC`&R-W1!A)Z90E8$jO3aGzEpM zSaLarW9Fg^&F8(vq=BIG>7~SpYVJ+6y{RGZuFzQ??V1o>$1cMYt;bw-*VxUek19#? zwEX@W*{rbogw39W#}Keq&9sOxIdgOtk{df{{+`Vk>e}3aF81cuDF%bU2FlHX{>UqK z5ptnrYh$>5UA44q!|;>_rcPU!(NAZn!J8-9AyagW6+AEcJ%MY}u=xycvCcxQYg7ZF z&Qb+vkVwz-<<=4G(BG1naXN;WL2`IBGj!qdv=4Hxqy+gZ@70j`Wnyp@sARFT79x5Z>Bf$uH=i$N?Ccu6B$NGa(fuEw&JhkM;}Is)Q%(;XzRs2#N5Q}?I9gKs9wsQd^9d( zxWXcYHDaB4Zn|HSvo*{d)vhz2xaYb~ZwZ{)s=d4u?Z^?ht_JPI_Z-AlVwnthmhd8i#-VtW)@N$T4N}Z zs1D!T6XC?Q3T+r(oMej!hjD=z8K#)kwxL#sa(;1~P(sa%5>*$6|AnfMn^6bVquwLu z8Oz)|8#AeWp%Iz#*GSIr;GU3Bp zv(#F(EF8j{nQvk)80vfU*)^t6u@HA@L?ak-i9={r+(uN!rigvNKRICw21%+0Bekcs z@+Ev?u6k-X&e^DI+3nmAS&GJ{)RnkIEv_4rJ0WAOT)t{&YBCs-9oi1*@It(LljFNk z6sxbiHhMt|<@Ub^N@FZuD+Hu}^eW%|_C_82r>5g zhNYlI2WzPL6A}xE{Jy2i3S)IZFo{)Ifb&<{t4n8ZFq=F@d{^jR*R}h!n5943{}8SKPTBNP(Mod29$<94pA{P6az#3eOE<5WUr=h)&%! zt}DQv#s|~&76q3LDlK;xhX++zE@4()ub>1ky(hg(Q&L&8<76ANFjztvGmhJv`Mbt$ zZQ!|fJ<~VaM53kjUtWFT+yBaRs*@VZ$=;<4OI6&+r|m)yt3;8W*fpGJDzHSKh1E_@ z(cCw!L+4@hBU@cPu;KQGPGEW;cpma}OjSHxAg>!#V6cR=3i0JCz!;A`JZzi&Q=gXs zp%!%AeGx<`W_T9Zw7vO_z398_8)Ify*~7J+Bm6*>bsg_p8i>DM+ZayuVymMtndiV= z`jc%A)(_eFoE6e6hg`Fs={)WKxk42_A|R~|3IHI0{$FQ+tiSZ8rd?COir`)I;~n8D z`^yuVM132f->G0xxy8!_DG5ItAINHq^4$A6;L4XF(4eGyi3|>A;LmuII2}*-No(l% z;_5}hUCP4}ifH0^=m#v_Hc8!YFv->RrN3de^G6|mA|r5f0b&Zq1sr-A8Q%Li{1XzS#lS{T2?>2efMCg zlQ?~XJO5L7oI=Md=~6a?H-NXeQBrnvriG?_TcL*FtHg%7PFIPC1&V9h?j3JZaH>U` zSK)WC`?A{{Pf`Qt*4xi%R|zAhbn1ettv<4ontM4tQzN}@b;;4j?2RPEn6f^cnA8YvRSRNM(qkgrgjo6VsQFIb)`!QORQ?jAKzY+)~PvkN{| zm+xXX+&y}>vXiGA{?vJw_sWjH^{B{uq;(rJ$yhkR4=D`K>mv9r(cBh-m(RIuJ-Y1( z=qbFZ$DLn%J%R-{@-BMLYQOUWG!a|rZ|r?q8TAZUgT`#dI}l3g|Ax>Ye9E`%)FuLh z$nF9#isl615mF00#Vmun=`<| zLAo#k5T{z@1+)@!9sfK?49#@hYe+R68*zwhY_8D z>oXDInBkU9&&`9WG{Kjig8`U0ko6rDmR1pYT|RqEfJF%Um3|h6o;VGlm>y(?T|_cG z816o-G#I079TXu#s&?-LOaE}faA+U_O)8dEbPPRxMg+T_SQcVwV|OvtOt-)#UaiV- z_53)Pxtt%ELMWf?boq+P_0r)kt`PIFqC=xpaO`@7c7t#Ufs`te~93#*=(dV|PLggwUXu&McfTN~( zlg&@X)>fhVmpBe13Z`e^Fm}FiYy+l|5@%e@cTtgWk|`C_2TD}=OYp$U_7e*=TF&Ey zMake2k?b-Cud=I0axBxCm`gB`;ye(AkFKH1m2s9H_5(db@WW!0q0Num5NZgTxE2Bm z+_V^eVw6;jr`&2mmk$e~)P~IaZsBYm&%{Wv#xB<$#>=RVcLbfV)LG}lA*qPOuOvZk z2z3{vR)$u3Nt{bSN<;zDoDts^m`LktvlV{u=RCg=?T$Jw~d`@R?k{ilmCc2WqA6#**R+a z%Ej8BEzqvMyqL{LJ(u94I`CiWxop3N@Lu` zw_j+i&Elq{(7gO~T18@}4`jFG%h5Y`R@L|@Zh_7sDS;zDtz2au*U`8GN>C}wz|zd2 zohaC+|H!z8X>U5)$qWbj6v(&MA8Ss9K^}kRsFMno?5Ry zNC(J(m`Pv9lA_)LjoTKw?2s&=LpEA8%~|2nev8XIrp&jSlM-D@oK}$;jLC*uw}f}d zQA%C3ZQ`9Sa!3Cgd_I+4j03O9NSX^xbBOe3h#M|7XKkv*2qQnA$;`z&NPAJEYatQ5 zx`hH#_gYa!q?-M)FytEUclhu=A-HMC6CZ{s(f{>qBppv*X z+^9z-CxOy|V3SyP(Bq6|3gDc^tSq8vs2j9$P*&60r_ucxVj0kk(cKi-qxfok6 z6uuyBiy%L!6Iu-3Pt+9m0K^qm9dq zxPg98nD^Cf>fQ6axPtHP^gZZ%=R38nIPaLFWxUsj@qU2e7SP@B9=d7>>YTQI^VIW! zKGCeQpO1864kKN=u2HfU&Z~+nxoeq-ElPK98oIPHQir{QVOkP1G+M8Axg^_WNWaGj zPn9rV7QSnq?p(?5{-aF?!|L@W{op9ahjsWb9A*E-(ZrR%IQpwbMY9pWK}r%@J8C}H zS=w-Xw9ZJT1{i5|@K=S3k)?2Lmq;6uWPEXhm-(IV*{Ph`X;T7vX}tEr1LG=qM2lqw z^X=_yDNU~s*4ja1BzW>N&GI$T`!yD?X!$7KG2qM8AtqgvIkEr?wYzj9wdOisVQhIZ zSh9trQw96?c0p%hph@h6^l-3;Kp~#KSYbj8UGb(GcSDcRZPZV(-E_hg^bP4e{uO1* zXPO}+ZJ+}pN`0AJ6e6D_b(*4-oD})j$a(W&vQV@DSGREW6kCojYI%358VXOAnGJWS{__pm6r55KaLf)7Xigrs|elnEvP z%;See!Bd6i#5J?N6)}ehd{FLb8dC0y7=4TyMZj274SN#=73sN$v1oOvvX#29wiTCb zeq~nHoc>q|$-ki;6uTSrpyS3~(qJ=Wa3_w6yE9hqqYn=d&4!*sS4bc0o0z^O?Zl9- zZ%$eztdSUHCE_$Toyw~)GsSmcBLk6g+$m5L7NrQ-@+8ji0mF9Q#z2xZ9N|xse_tf7 z+9S!ZlF)F0jSzk7EGc=&_VKuJx|-=s^$<;k?u$>>?UK9Z$&6e~C7Dh3wPdGY@sbrw zj6^hsw0VN2d6itr@o_-39)TEI1bz~-%1g!<=UAaa`vwsl*V1Fc?zce}IV9FcoP#!m zgB!4(ibh?eP=!Gp|ES|{Am{1LL`ZxJnfvsdMMp2u91z)2Mq&*x%v-+%r@U)Sw^yF9*PJNiL>)i==n`ZSi( z{eH!HSG&E6O5f#ueSMUGzTtg$ARB|byn=(9dPx{ocUiq#Hz?`ePt*{hTYrGE|juckR-aDh%T_+fcThT5Uy0^f? zv@KN|c6*Lc6A(H}D(Ij`szBEuXb?KqXG{>deEoghx~9L>a*oH#h*icOz)B?I| zk)B8%`fNObTh|ep)`^eq$ED7UJ~x9L?!j`gz{ac9-wi2x#c{iLka}7@xxW3b*-tby}uh8MHu2n;S-k7nuIp{ zUZ#>qRy3US<|m*%{N0FI$_3CvhWPew@slVBb<-3Nv~w?QJ8B=>uS0se>pW*&Aedej zm<0FD_umoZE&aLmgCU?`(AWG!QQx$LvMMyJ&J!~hGg#q)QAAWakrQ^KzD6R(2>E@5e88u%^f1d_?XiombewlrcGQ`Be19o7nbrARV?1G* zcB3(PcYnukk=EhmH%fMKeV8rVQMboTn0P)0`mJ5S)f=Q^Iue|rbVa_6yww75g6J{* zQ!34BA~EfbJsB@K({judjE11`T2E%U)SZ$WGg+=-VTdy|YR**Ei_DE%Ow_E5X=`O$ zdKolm{F57{R+M0hMDd{p&&7Jxj}xCmC0F$nFp8yF41FmRYGU;FdOgis=OAJYYjCR8ym;29FN=5)t5V{GHJ+qZT!8@5yyr zZ`5p|BYcJW)hN5~Q&{!*v@A6~hU`*$Z{7(VcWOECeW&ai8~)s=BbfQV zY4JCz;~!25M!Y=^2s_VJH97Ba*0_cMA&9%55PkPC*&*w0$6 zV`nP(O}#jIEx6f}XFx6eDPb4$nxO@{^PN4~??H$W5mLJ2=#5*?+g(~qUiKG=1X(YQ zqq*wVE*3#1jXV?}oiYVer}LaaI#rq%upVo*(>UChoy4$k%X2nzCu~rS4$Ybn7No{L z1?r{XNfKP8&DcM4e-cm|>g`c_8+8FZShP32|8eERJ@1QE;fJ$R_-I*z{YUzBbaJ;c zcKplE6)Ia>t}r0HeO~lMcs?I>*`a}K8k_kccQ}}fds<;I7FMbgNn$!N`1T^>qJzWP zH0={6b1>^Tx#p-alOY4uU^wVd#e}0Bgu$qM99|(E-2EA~7l!zxw7hgG+}LfBrfX!Q zTe4~e`o6_@obZbrYrp0}Iw)b&DwFtGl1-{VeIsi;=VCf+U6g)9{aY*w30yyATp~3$ z0gcfUFF}0zK@+3|E%xzaI`sKH51L55)~anHs}t=P3v_9-ur@ObgCA*z@#`-{F}p?6 zDhuaOc45_)I`t_|2Szex?jk_D3xvXukUJ5}TsIPhkc?JConGv+mQ|U;MK(#mjDDrI zz1!+X7v+~Z9gd-+iZ8%tr2w*l;@Qa>W&t>2;8`a;;S#Jus|vjf!7sJx5_I+&4} z=I0f7oiejy$Jz-VH4J(+c}uro-*sBvaaC7gobkRrnpCZ?B&pYtgmAiNtw}y1lISa+IdcD{B-lT+BDnHdj<7Ka*a_jVs=E zm1w1AzwPZW6Bh%uLg9-IWb~IKso6)nK8Mu>f&>Z1H>{g3ftSFG$SJGRZ=wgjCTGWY z1-rgD2*h=-9VQ5>wi1+(tPz#-iUwOw%ABB(R4%D?#Mxs=;@VCg)rB=@9p?E)7iqqH zg?%t>WyOX5!=L-%A;HR>8+&)*QQ~L`>8Q9zbS7JZWr2wql?5lP2)^Ig@aEfu_e{2% zCjpIvD`oI%6M99G_v}9J$1VR)i(l3f9MAvpP)t7_3gSmwq>-(moP({MBfX)mgYjSE z%*W<||LcbQxOIu~y0U!?DBm`uU*RM-nydNvQmlnYxy22LgmyG%b#m>0CNyzA-n(Fr zBYIucJ!HOlOi156@vBU;sgXBOA&4uW_!*=_H&RcTbVgpt?Q$z;s%Y^0MQ=`ZbaalS zjifeR8Z-i)fVNP?CXS_2POW<;=$|g)kvNOnl@i!ACjAI0Sko6=B3}c_1vyi8P- zwFi!rmK7o=G3A-@S}F&%3(f z>y?{%XEJaj?R1`>D51)H5<9i+PRTcquu*x5bHn(-BP(gLr9h*dBhj1ZZ$HUQ*kVgy z-iao_2AFflujUpg{#;p$hqGkp{@~r#$HWuiUwNl*XZL@!`{3I@kIV#q%bmX}i_kCl zk>^pX=L!#+)cvP;$p}Nr!~4u#nEfhbe`DXvGH<5WVhd@e#l2m zP!}M(bcU)2YNTr&G3nE<;H$dQT!V0iESpI!BJ#A$cKaY&n$mD892V&1dG}EfwePNu zL1AA;5S-+q41VIYP#(%ZjHmu@m<5S z29Ws{(AXHyx0QTEPnvaAoO^ggP>1G470%bp-|8A3ZBFxeOTmg{p#{3r@MXDRvn1F} z)B+ry#`?f8V@Yk zL|4YNr13d>{SG)U72X95SPWr5R8#oV^TszUoGe1P3m>9$Ot5u8@-ZxuVLyVg;zZ(_ zPxlJED|v$+zU&nji3JueSJO7UCQDF@Ovcw@Y&nF@MxeylsX zJ0p5S3(1u6@XvWOdHz_#`^R7l2t@ml?f$&C@_!!k|D6A@(^5|I-vR!8o7evY{&jBp zP}09_`uaWa_pL8~hpv3s%ilJ_{2u)8&GUbU0szc^bmjc_R{Gy@e(#U|8;R?~;QqI6 z>EBU)Z5_75n(8fSk;`MtOAZxrZ{TGt;?es%i&j`DkV(%&dCXn#cc)vNS7 z%I~cff1^NP{SoC?ffPiO#cY| z-Ms#e@bBL0?|1-!GiCsQ|8QErhyQzu{MT?})_)2AXTp?|1pUzQUnUL_z~W>3fd6#IO= zJ10(3N>m{={ZX*gN`O$MAT^RJ0YXJWMdF8lLHmc6s0brO%MT^ifa>a`yjI$eZQ_DINO^B}Ybzk6ZeOQT4RaB`i|PWGZYT`2x5g9n z!u|5rjgr1CbS7EIOlH*WP%4`r%K?mdNtLE1CQ_r>WIi>SR`r@*>-B?{bcfJukgjVW zChbIkxTg0=ZwC@E+=1*vZR4=i$ZCLlAU=rV-(wx|o*sd###E2vaCg^ndO;taprg>& z+TC-ct5da0ETiF@8W;mLZ~#lGyiUOy~e(+u{*K`h#E{3a@e0!YBW1PUklZ)YMpW=Mu zGn@*W01iO~-EY0*+hyJ2+VZ^?=4Tg=aYlw(K60*6jVOc?lH_70nI1{z2J+`Jo+>A~ zYtqZ9Y;uBXYCu1CyZPK1FPl?K0X;`kr01=%q3O#F`^@KVQ_p$n;mMIydQ^3WmtcR# zd)xMsBo5NEm(5OQaUBZwlgnpQ!+EoxYV~I6k<iS_sK3p|b6Ib6YCM_6cD8X_^1`%kPzZ&%K-4nOsiuqePbe^0-~@3U zRqcesVfA2C+SwFpuVIh^r>X2tgr`8jS31A@w2!Ca>tejSJAM+M@!^ZFi~AZLpl%R8 zrcz#a?dvnk50X6`uH7a#mLw+S^LJSQq_|nq%%_UO;WVi*CwymvW-3Pg=WZJWmk&zy z+-HizC8T;@&WE$x+6bgsiyouzv zcMKzu1^xBE&;zmedtl`8UU=r&K8XIJ4<6im0{;E(A45kzS_r*)?Jptk{;KdRuSdgY zcW(_}`%{1Tk#9X2j{jyL{P%Zq;qPC~hcE9r8@3IGhv=>j0}cZY0}cZY0}caggn>Dmfsz`k6i!2*q#7Bje1!_uVnZ%ZyS{UO!~KOH=k1534BY|*e;3Qr1hxf^zo*EK5p($CZ@*o zDQw?=-|gysNsn(DNRNAGym8z%l-vhseLgB1gHrnZCd3K7(;XN|MYHSigQCYTR!qxP zk1r=WGxw9U&!b(VKfa}OM)RQQY#=z=gF1)&S1P7Q$%VF=Q-t_{H!)n-N- z4$_4-OX$?(Uw(wS*iIi)LLa&X9*?6B#zQH-)cbgyntVwZwd4JQ;9U~#4jkSY5kWpP z1fJ?uerSAh_%s>9#-}`5KP8{adlQr`%zraw zRuOD+(qG3{r?(X5S?o6bGB#hYh$7`gA+260XVnWdeQWuDu|Q|_s>KkS>y*ut3bdV1 z$g7Yy-_x{1+Uf3Pp*u~X`~prcF&^~%Uf#|5X-*7zvP;PmlU@z=amhOG+jCi-yjPg# z;`Ur~UU`LYRwJ>`>#t>AE5n}a%l}(rzoGrlL4T6}o-}h&(2fvVcg%f8Jey6DKfs4) zyqwLVNW}FlxK4G*10h)6FFb!Q6Yfg=!tdS3@Ci=bb%SBR<`*7&h!LUXV%@`>_t!mH zuW*-2^nJa4;X=FU%cC&E3GzWq-IekS`TSDcKehVbZ76jH^%3XKqzUo$ob~Z%)_q=) z!3zADbzfk}%2@w`{>-`0@#t;Wm{iQM+tdaI%~I2UzzU)&aQRVTR`^b`OA@AjfaV96 zR4pnPl-%rB78NhaB&1|cnyP8NxocInOwTjeSzN}uk+@9*0dX~)}CoYi_L|4 zUH;tMe*u2*AKb+K6JY<}0N(g3K>W`DJNqN>z2OKvbT9&+-4=ll?}@!!A&#U3LJF4MlUDfc@^VMJ*jBW6)++o0Bz+u2)z+u2)U{x5<`=^ZPQ(C`TPEW6F zKWC%p_s-8rgd%3dLRw7!H2oth@u!&js`gX*`f<7XIa`H3w1u46$=S6T4>!-xc~tQ3 z{G2zNFHJtyeoCJ&*VoT^QS8gwi)8mpoTw&vXjk>b#$Yn`8yadbo9t-g8{;KDlqU;7on3-bVP zy$jI!0l=F>Rj{qA3R*5z!SJyvIQVcC9DT70j(sBnPdpoeh2KTsfnA&7$G?m~{h`gy z&smyY9fZSx!+^tp!+^uUT3|r$%lbv1()!h{=jW^w{oeUGiEf;qGk9*#UZD@?=Uk3H zZl0fWzu?{ZIhVtG(Ee-w{?+tbuH^RzCj7_k=NuFJvi2g`?J|#66kpODQXu~(**-S9 zpYwp&b9{R*TZGEr&)F(;rzuoTtNYHJ(NH)CBqQfTPD^?D`#E2hd94gPuP^^^jqfeA zpEG#gsQ;e)oyU6lIcMb@)5U(yu=O17%Sk5mL814?3w)E2yi0Cv3U0G> zuSm{E`bhHry07ulsb77bvD`WlB=UYd4&@eM7HH9uJKH4|~QSpMVy7gh0{8R=6%b>X#4v1!D2$mjbpK7x^?#~zFg7=L&a zptTd-zuf>$hwx0D7J%2=z`1h~H!e5~I1D%pI1GHe7|{E&g|j@4gbZiB{hYX*s!+3y&kgqZk?YNKYm4*ce#e&zWlW!%Z*+S7eA%d43n@v6 z=YksNmeV}wc862>)OjzR8%t#rd<`_qb9bumu-HFGPcMd~a{-IBQ|cUB&0RXx>rX=L z&FyLw#pHNzMe(V;&YJl>4ZrC=YFqMj&aKMJ_j4uBxsB<81yY;b^!-<>JI7Yb58HY4 zB3AgHBjT7$`9c19yWBn{^x%FcK~t&uTc_7AKkBI&Y=`|#ru?`$zmq6_9=(S|7irmcMVGQ`27fKdQd|zqa39`}4^C1@q?oRwoULd+@M^Y z-o}W~^E)X2r+HfNe>Wbza#YQ!QRjfa!66 z%*)-emWK9iR=<`5QDq6KN#v~{s^X$l$Icgr)7>4=gisuO-D^$$XN|a7jbXjzqTe5{ YgXVtN+~3~lckRLd8yoC*t$*+Ff0W+y8vpAPB(_+XF@q8jKghMKsYE z6Q#?$5aWS_sOX7xV*=sQ#0&9aFD4`=2Dv~?OgNa>L=VpFENw~rz}>_C-JSgk1)SV}U}F2QA}xRoa&>K&h5 zD&;{!)4mhTbcl{|k!R8b_lbva+N)s3YQ3;S@bkPd>)~Q^xvUv)9N_toIWHI9I!|ur zaU{aOGIrQ%h^WrJ$D~)I z#*XN;3FgrSmqXR6F;pUgATi~NiX@Gx5+7{1S)8QtIBGOrsbTRPjdMtxoyHWpu-Qdfjl$}otz>ve)jnwe%Lf0mX9zsPJt+mCIDD!Vk%2>*;b|~1XyoT zwb*2A9YbyrTwZ+WZKve*q2OB^Ots)q_hW-gH)H}>S!UY$Z#3{BfNc_=Uc}~O;J?@; z1Di%OJJ$*&DICRUJ{WDqv7__wHGpTTWs6Gh@C-vVXSNbU^pZ4TN!Viy(hHs>jzRU89;O%kmSIl6-7Sy-FnSMRgC>eLB#+wmt*Rf2)V0l)(g0RR9c0M7Aj$R7#-kVmX1pn?XaPB;#* zW5_^K{-7teYMJJ<$lwOakWb)!{SEWOTat=o{h9z7v=!x3UZKtitKJxeyY6=Jjrq`$ z-%yM3Q}E#2ak2*(Ek@i*Hq|$^0dnFdY>eq!P@*a;HNF&RFWk{Z@L?H`z0fx99aC2; zB->0|oR*yfk2`07L~CHaGRpSiCpG$Zq@wl=~WY7IMRG&FHK3BV`o6;pe zK?Xf5rupM|XmiD6D7|{m!gYjFmxp{g=a2rvCT|&nSU$l8`d(uDQ^T)3HOUytp*L)W zRH!}bPxEaL5ga%Bb@DJJ-)=ZzY#L-_Naeo^Vql>FjK0!TDbOl*?}=`M$XTh;l^~N8 zw-C4;Y5`xCoi<0LQDr55N4-|V+YSg2SW>kB+P0}^aBO}a0kCfA^T1_ zOm~qy{c5{OP!W6@dsw{rVbLl6E3Xv#`B+vv)qOwo{|}4g7ODV zmQZU+gCpG_rXi4%b%-7Rs;V|6lv2y0&mjA_Pf&fi;sz(GN-f6M#ZAdEQPU7dUYbFq zsWs%~LcRg#DA>LuIGcOp>Tyxi8p&Vn4o$Jzz;qpOQe@h6d4;B~cWd=KsZ*>UqUklR z#+SF-Rz&PNgUZhC9$GS-vH}~C#O&3O{Msiz;0E_r=r+iiG$dZv)jixx`p{W=xJ#w* zT2^oKTe{6t6AyOzq6=;4w*OqFkZI$>>REX5{nxUZ;LJmG&2R5hD%7c1@dPmW^D0D{ zPsn;#jUBL?i$gUgP^-ijt*CP@GLrUywn|rs(qxlT7T$)&?dn*O9BgowJXrjt%>Xj8 z=RdmB9xb4~VCcaVgeNa0g75+8fS_9TLS|{ZfOzSGrmE};m$s*o{%2aon*)&|(9Ziy zizosqwn3Ucm!8Q)@d>V;!qeS*^jMh;)DSe6I!q zKGI48`NTaMmdRSBpjkn?Z*#SR>2Opv915sLKmvqehdtWLv+QgtCQmhyaWB?Kco;?~ zQ_b)NC!w^gVz0~Xw~!zzeJN%Ojaa`o&lhs#Y5YOwJ1IfttPSjbzW}Njea483;^~us zinW^J`-$~~9-*fXu4?ivKJbyh{d!FrSOE1l4=t694A|xo&+s3 zaZMl}qQ}fVnR+}HVU3#&chjk(C?|f|2o*7y6&_u{+LYo3uB;TX^+Cg<=an`t-Z;vN zfuka1T4p&k3i{It18~YQxrMdF4K-Oft!1Y8Ovy%F%Y33E+}{(*+@Y(lT@z}*l5Fnh zPu9pE$g$fSry-{iA5kbxja&AZJ`57o_rhOmeW9E@YeqSDt~OQ3~3 zr1IriNF(&fT{GL=gL|J*$^w2v?t?)v^!%|NwrFIn{-mQx_yBzp_7@IFT;FVL*qQA# zOF*EZCFHxB?hcg`h8yMm0)5K z8R$6Spui0aUR=6k%QiEVqUTr;Vd!|*d{SI82h!|HdyvuY12ZVLKUDTNWLP>g!g7@b zALd2m(!JJJPc(enDZjBp7~dr9?`>)~1{y19W+GP^YoI)MH=;zPUp=xj&9F1!PcCsy zF2RAl!xi+|>#fpP;K07&3OUovw!o!1@heAQ3EXDSLe*K}s&|X!UQ>9XQ+u(5P!moH zg~sI5Qq6 zc{?8s>lq01i}R<=8(;m|C?Y-HyJIO2x%v5im^lOPm9=u$qp9Q9kJIszQLQKGmh-x_ z(D%2_7_Fe%7hsf{IGl7P1jt_P10@8RWd9HwdIr^{oQx|lJe4H-QnkJt*7Fi zYNe#i0Y-X`>`n7?9})?s=lkWCi^Bee*=vlCP`O{nj%ZAlToSo59zhD6wbYegl;ZEM zEKOf?<^@dLUVX$PV-S)xKb1C+(ChgX3+)vKi)Z&zdc=fL{^~E9qfVTg=ZeSv@Klw_a#@ft zW=U(dd_t@xhYpOAbkLV(!A}^t;eLrR6<1fshvO&wRknEr1Y^j~9_fYkZUCDdq^w(x z&rwq@-N6*U454Gt^3(bnU~~}p^V`6{`b{?CXra}igpE;D4>$D*%Vg|4pYUru6n!#+ z>Dj&}9Mi2^8b(NU?V|5|i&t_^1LMd&Qs`dTa{fGcfB$ zf={u}A4fU7HW-##7KkD1!E?&sDKd79%Ij5- zF)_tIHLM+g?@MeImNHhJ+fJdtCq)Qk@?or{!P%0IUQ_d}}W+4_2zL zH>V~r@QSIJ8$k5YmWf&WMM~uok1%sXWrj54iCd}VM5UvEdpY4SI|kNE*)aR4cGX}< zM_5Z#r-N%zuqmi9U*2%+qv0m;kv98rk&c_o5Uz{8D`%c}Ck)JW#D%)|K{y9?VM|1tXVZH7$ z{Gh}nJjh!PY_2iQ>+pP{jOCd)snHaB*db=()r0H4fHv1Vuzm)mvS=x?{}4wfQm zxpQDY8H<0lZ(}1`D@O%bf+_aZ2H5*Z|JHShL(Q#u9@dVt-r?0&`knO#9d4P zc8iAG?$aOBGWu3fz1y_W$ASIw{6K3fMZaO-vsw!4*f+T`N^go~f>bl*8G)0rsIx!0 z+7gA7u7I@;2+W;H%P{ z22uCXXr|%H`9(Z3T!P6D++mMANBTLeYwe=KpP66&v3WRjFOPvEuo4y-I06?JL_ZA$ z#YB;RE>lur>*o{19)&m(dGyko~Ll7}6>4e#=7y@ZrSMY1&Cf44;qjU!{*__^4K7>!ik_Gncte zDrpBMIVWns#hW^PQ)lm}1mPcKggLA?x@O{8?y+OWdtcHhnBiUBr#DLS&;?s)LiGw4 zmvprqTv2-p;5v=E;@ot>#yDh_{4C<-i4)H4I!9qW<_&S{rA`|e=?p{2i0%AZ#qiMGDb(@kgc^XM=a!?_&SrV;`L6mP4G?YoLu45n*A-O7n@KrG#PR&dhiEir`7b2&jY z(@tk1jwbS(|4r(&7jIJ6{vh>1|Ch~v^)7{x#!~fe80h%Z8dUoussOCl{xZ>$L(?xi z-|3A~E$FQ?lZA=Gn9v+?yE+f1u%5Wu5~4mSC$U^l#2%Eb<=;(UJhCupA-4s^9w+SX zWxaR5pA7kc+uc^%lx-o)wq0*LhuuV*!v#k_FY%GgsuWJOU8RC+KH32;#9+|-y>=cm zbA5V)v?2O9)*$$&R4w{?`$aJVrg%M;Rt3? z_5JjNt{O7`3O$u+_-0}r%J8qy{ZJu1JNgD>744hdpC@1c5jwKoN}XdeECAq&4OYb> z1giq!K|1SS-sXM&f&QVFRf;84gCdG@kmRmNvH03Z~0()$lUwbTSJblDX zioYz}C!>((xw;tN+){ViI5jb7q&j&taC^iZA_n0B4>f_-Tb>VZ1E`l4f zOfpLC%v~~1mBT%*`e)7^HR74P#^;KSX@x#2joEYCX`dj- zf{ce0@)QnURf%>9;QW=^1Em;GA*s^Z{;YQLmamqattum}l{&qpWz{dWCpzb|_0KW{ z-iHGMg0s8dc+bL~o6x&M5SH&=#Qa+YO7hvQN^pO7E#{30QUy8w;`M-f2G2p%?~V3u z?!g)T`iVRD&p4 zh)3CS^qPsYu(?-E;hnqyix*9>b{k><{^Wy)1z7>qI8a#>;W>YXEGK6RB)=h&B7y54 z)9e{cb?D1VrQb7a2z|hqFOMPrD#Y&{uLRp)^jRA929tw%wqhq_+24MYWk~GjZiMih zK(OZ}KRF`V0ktWVEs!ew*2NAaigpzNtd$XI2@FHmS(qVES4i&qzm1qEQiaoA+bXtL z?P)AP_k?sBpukS6mSEAI+mktNWBkyNOlijFaHo>Q9Atb@Zbb>#*o<6=MM$?<716g+=2P`v#5RN732=EAKAcV94RNow^iGf>Wi871`SC5W}wcsMMb5>u!E%lS$0Cv zEkftICUp>=N4_`Db-^G7unOneW~U;Wel@mKo){|o3a33#RK_}94)ZIK?1Nn)x-mdV~~KGS4xlt{GTbhH;7-CPu+oubUeD{yw}L))Y(=6m1@-(C2uJ2ySCW z^Heyyw_W*^G0&_bK^Hq{2#xzeqXpXXx>--r59G8Fa*ezgD?~k=J;{kH?AryP?|WXQh#E3o-NKqu=L(t-al(!xGP;qd(8&6=l|<80kue3csd%k-fi@ZQ3VhBH|zrBNe3`mY;M27fBjE)4ZEj+J0Lty)O*`Y?Wz$(k-T- znX^yhyzDb4#v!27-|4st{vQn+xDxzzaTQ?5D?^NoDwtQBv!cQAA%PDvNqODV0~H;^ zvh7<(Hh3u~c8NJz)AA`fN$^j#@ed_SOVPja++@_dy9`rpboKj&+ych4W|qoZ8Rixt zgY8vGN7zHpLGnqgqKxS=M}HbfhGd#bVNDM6ZWf(>&34i%bU2C3A(rx3HtO? zWl6O8_8uhHykhaVf4lIc+w+=w5Mc&0tkSQPa=3WTHU1OlC(7gx6T#0m$=u&O4N@*p z#&Cm2+Gr}SXUE&!IzKF~`RSmJ*dm@f5LZ3(IkTLthJ5>;E>h@;fTqe1KMNQm7=p=% zx$NKTcNjZ8a~$05cd&1F;GF1``_Z9{G#bn~;Wh;_A1kroIwu^aG2XR%El75RkmCtG z^JUy(kPl9SMi?YNimhqNW+IVc#0-nIjG|5!{Z3q^|;%Y#a|e~c{GO>qT|Fuy}gslgNk_=TeNsR<*ZDR9Y(Re7RO zTG$49J_9s;`{l=~^9!LU9hbkzrZ<32Yi7;lZi*)s==ETezxIPVy#XyR)Ff-bDm9A8 zjUJIgtkUD;FF7Qb(gKap@y12baTNVmG{L!WSePG?rjj_!ZBwdY zsQDEn%H#{oHQ(Eoo54=+^m~jn?P7(f;Tkf)M3YK}TfWQq7t=RMHU()Avb_q$%1XyK zg7=D#6boFeh+c>pf>%@4<{CBjq^hMq;_`=4%}kLTVTso<5FjBdc9K(n&)}D3DV#o2%YXXUP(XWh*V9_hTNYKKW_wN-Mz--Tyrl~s4IxTS(eRzwh?oNmKy@W z%$K}oS+jdI370AdS}|S=D5L}R_un4|Bux^JW$w*2N_7~lfDEuGN#HkWP}9W<44X1x zEB&0XgzrUB*bU?sUai=U)^V^L^>shdp*)h!P&o=s_^gY8)F1TY0`!!H9Fu)*1LaoW zc}k{b&ZO4Ah&45*MDd&7s@y&)mv(~uf!#CW=HP4lN1}1Y@H^u%>Glk?5@bc;w2?pWYM$WK;>mEI|_yNCJFn|DJ z00Iqq2j%j*htySm7D2~odV!OlUUXLhl|q)?GBKE7>?1R z*hs9DerHa=j2S_mN0V;2$f27wkXg`#q!TvBzgGbC2lH7~_*m>{^`}l8$IX*X<<@XE z_N6hbGc-~mD!9)&k8CI#%aaz`8nR9uxLb)gl1>P9%G@Tplk4<}KL`hVpUYn7SYo#` z!1(~ro4g({90FIA_0eqdeLv`w8MY=K+R-qC%zP3hvYzv&#FjnNQ zLsNEMHkUC=ZL+|Kx1vUCQ|xTVUveN&Mx$*iO5Nl2JL>BW(Bgp{007U!Ve$`y*ZaGgPIENg9GN7Jv+*L1^Hc9`GRV&wH{&dCx339FRbFicednWTr0 z4uw7*YW#kf|HZIhx#m&>bS_``@x#U-BDG|W6vUR18J+nt-&`{F{b0$DPZg=z4r?+N zCMN55RY?aas{*0dnCOWXKI3MI=Sqer)6&<$HrpRNyR^IYGDJ;3az1p_(tl;JtJ67> zsoB!gm}i*3w9rHPwgmV4Ib1)L$O%i7pyvbVEgrIjQhXT0urhMrl2aU8E#x{u!*l&7 zEuhy3`vg0MfUzv7-0I8G1PsEi%hkz;{0zQyo_MA3$Io1RAdQa5=L5#ve2b*_`VBhD z&@)JM-V!-ll6iYW22UJCt8@`adnU$XJTfOzLFv0mLBJ>9xzseGjwS7KgCK`}eUvMTwq;39L~{&g z3bHzrzZ3SWz&N>;w6#dbFg^R-Kj~Ob(wlZcdkEAs`^A{Q*2tdu#Ar@j0vLKGK%lR2 zWV_=h_agBW?uXWLgb<=z2d`pU`YAcImKlE|5fB7)U*NqhHq&^ZV`ys@(0brS673)V z1#g6YR%WHt9H*@qpToo2nO-vCZd491EQM0Uz|7X{5-PtexveU6IZF6bMzRYKuRTCH z`-_u4AI&97Oi|5?%AL5~Qtu^&`$OG@eaICF%7+I z{V}nF(=BBQMeh+ejJV6DFKuIAp#M5MGP|Y3cEX#EjsK-%{(tFM&*{zDN4MzDgeZFH zSAK*bYAI+tpo2>{Hc8E}DgzVGXsS#7A*6$nYo+owB@}SeSFcagz=o`dQcpB}8_IQB zDz{jYO-|it&ra#yfXykaeQT-pq@{)frQc&b9pA(P#=fX;oRS`mK!s_u%}%GYdKgT3 zb`|)7ScNu&OOh%I;2js27ISu+exdU;pPG=g194f1aMV5G!mAkj?>{y859)D6G8k&W zJo(ZK`m5Wm@VElCAdikC=JP>AvI_&kcZFj%I)5zI6=nE%_MgR}$FmXO#L!t^aY{h} zm|rp9E3hzeAy{}B_q+$~uU4b`?y?`%kSbrY(sY0wt~rT!LMHmvdFs*C!HfD63j;3s1Le5o}lCFM1C4(cb|^lWVtFD&iXs_Ozc^K zSX+c(s3^tPYswDxGVS2^;5pscNGO*Uz6)zyAB&$-l%AIxz$UG^8;N9CP5#*o=8Ci# zyH41f6af%DIx*$0A^JG#i*WfC&B8?{!U}qcAo0i3Sas(%T>6TlW52=;8&ibdUs0hM zmIV^&t3XYIabBW!NyjoW?ZeO|0_wGWs~eaDM-vc(>=U~`46$Q``q6Cm zyUqxZAY!Th`}kUpYt`Y5$ZXiaYTxkUnA-H^w<^y z+`rzev|beHPHiQSLGCsQ=YkL~%!5qQ56)Y-E1z=+wVsC2_1OBQpX~g)e_V3 z>l-cIwDo6d=VeS3+Ca}o$ed@T!C_4N54U^%qj3BBk6OQhOW!^nMbg2_eQe5twN*)~cqILvXfM(>JpCQQXyXX>SL!q~xqdVUy+xF3N$>4X<30G8`stf0y{N zq|HGN`qDBKex;%for~cbg6mC}Zq)`iVo6a*t$qn*(__B~1v{!51bs$YiZtA7h_2l( z#^ydg_O)0n{rd@l*CbvSJ_2O(TmK5T^4$AQ0!p#ri_@~S8e3+MPA0VSF<6y$xI;U= zA0J)GSu)h$TX}OkdFgN*Et_oUA?@xsJ9a0-2sJa!7D-4`YOkg7zjK-&J+KUmMk?^) zpyg=a_^vBhbmIOp+Y{i~8@GzSz%dAl~t1;~W6^~hfREAz;2m31`zw)zYO+i1Z_ z0R@t9T4zTMyMVbPN8KQ9-%!N~+AWml`(0-OmE17~}86NNd!jv2JFH-C!NsJ3JkZlV=EK$@j#=-wmX3Y>X#gVC0Xd zBq|Q2W@eGbEy`0;=oo;v9%-;BaW4oOn*r6O)G)`5_?3A9*BEN&et+5(Eo7-{yEMxu zz&djH>}0*;J}Re@6=Jphe#6Iz7?}8Kakp{2J(j}p(6Fq-B!G)MU#)f#92LQ=<@!g{ z4#>kQz|T}br+K}iijKJPUE3Jiq9X1F&%>RP@+NE4lQzkU;+HdCVVH5B?8s01!6Hzt zoeGD!n1OczGDlL9Jwc|w!0iXS8-dOfpnf({XCPE)n8r|C6wh3|D;#Am2P2`PwF`$vVr;mt|Sjp#6aBkA56W`-o`OrUAMM_Z6 zEP(%C$rk{C{pKM5z3EATy?KO$THbCpa<#B`V`KfhSN$K3f0rjA0RZU#MX(MJ=H;a% z`FBMFJODuSUx0mN@Hr0)%-_{}Cq6uA1t9p97Z;q(hx@P9%>rILXdjG!&Q0^;lKiv$ z^Y&xze=Aob_%EI`>^Bf2$$!%3{|jis`-epp2nUYi#US~cv)cP7zy80#0OEgu&Qx$< zYkmxpf6wy%-zF*k0enP0s0v&LJ ji3O-h{uAK<0LFg-4EVsG1c=Zu-|n-0>l_oozgPbcco_}t delta 8981 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 diff --git a/larray/tests/data/examples/immigration.csv b/larray/tests/data/examples/immigration.csv new file mode 100644 index 000000000..eb4331e7d --- /dev/null +++ b/larray/tests/data/examples/immigration.csv @@ -0,0 +1,19 @@ +country,citizenship,gender\time,2013,2014,2015 +Belgium,Belgium,Male,8822,10512,11378 +Belgium,Belgium,Female,5727,6301,6486 +Belgium,Luxembourg,Male,102,117,105 +Belgium,Luxembourg,Female,117,123,114 +Belgium,Netherlands,Male,4185,4222,4183 +Belgium,Netherlands,Female,3737,3844,3942 +Luxembourg,Belgium,Male,896,937,880 +Luxembourg,Belgium,Female,574,655,622 +Luxembourg,Luxembourg,Male,694,722,660 +Luxembourg,Luxembourg,Female,607,586,535 +Luxembourg,Netherlands,Male,160,165,147 +Luxembourg,Netherlands,Female,92,97,85 +Netherlands,Belgium,Male,1063,1141,1113 +Netherlands,Belgium,Female,980,1071,1181 +Netherlands,Luxembourg,Male,23,43,59 +Netherlands,Luxembourg,Female,24,34,46 +Netherlands,Netherlands,Male,19374,20037,21119 +Netherlands,Netherlands,Female,16945,17411,18084 diff --git a/larray/tests/data/examples/pop.csv b/larray/tests/data/examples/pop.csv index 4bc913d7d..adcf4e093 100644 --- a/larray/tests/data/examples/pop.csv +++ b/larray/tests/data/examples/pop.csv @@ -1,7 +1,7 @@ country,gender\time,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 -France,Male,31772665,31936596,32175328 -France,Female,33827685,34005671,34280951 +France,Male,31772665,32045129,32174258 +France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 Germany,Female,41142770,41210540,41362080 diff --git a/larray/tests/data/examples/pop_missing_axis_name.csv b/larray/tests/data/examples/pop_missing_axis_name.csv index ab80633d6..9b917b13d 100644 --- a/larray/tests/data/examples/pop_missing_axis_name.csv +++ b/larray/tests/data/examples/pop_missing_axis_name.csv @@ -1,7 +1,7 @@ country,gender,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 -France,Male,31772665,31936596,32175328 -France,Female,33827685,34005671,34280951 +France,Male,31772665,32045129,32174258 +France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 Germany,Female,41142770,41210540,41362080 diff --git a/larray/tests/data/examples/pop_missing_values.csv b/larray/tests/data/examples/pop_missing_values.csv index 4f9647ff0..1167018d0 100644 --- a/larray/tests/data/examples/pop_missing_values.csv +++ b/larray/tests/data/examples/pop_missing_values.csv @@ -1,5 +1,5 @@ country,gender\time,2013,2014,2015 Belgium,Male,5472856,5493792,5524068 Belgium,Female,5665118,5687048,5713206 -France,Female,33827685,34005671,34280951 +France,Female,33827685,34120851,34283895 Germany,Male,39380976,39556923,39835457 diff --git a/larray/tests/data/examples/pop_narrow_format.csv b/larray/tests/data/examples/pop_narrow_format.csv index 6c68bd758..ad804bf19 100644 --- a/larray/tests/data/examples/pop_narrow_format.csv +++ b/larray/tests/data/examples/pop_narrow_format.csv @@ -3,5 +3,5 @@ Belgium,2013,11137974 Belgium,2014,11180840 Belgium,2015,11237274 France,2013,65600350 -France,2014,65942267 -France,2015,66456279 +France,2014,66165980 +France,2015,66458153 diff --git a/larray/tests/data/pop_only.xlsx b/larray/tests/data/pop_only.xlsx index ccaacacb11333a838bf61ba48214b723216d2e93..153afd30a012366fc0468a8d0761bc2723de6a26 100644 GIT binary patch delta 4229 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 Q5Z%S>yLiKZ;Ga+b0cS0NAOHXW delta 4970 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 diff --git a/larray/tests/data/population_session.h5 b/larray/tests/data/population_session.h5 deleted file mode 100644 index 24f3adf294c7deb34ad533c2bfae8d52eb4689d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3190240 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 z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7e?(geOzWE*~C%-gfHS>z%>fB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72+R@qQjr>dV;r?+&J+m(1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ tfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfWXoO{vS@HLZJWv diff --git a/larray/tests/data/population_session.xlsx b/larray/tests/data/population_session.xlsx deleted file mode 100644 index 00bd78f0108347f4ed6966a09e2b9ba05e454517..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12122 zcmeHt1y@_`wsi`{39iN69ZGSh#a)XRcL?rMg1b8uTC})pad#;0P$*7u|I(g&?)#pe zp6?Ibvol8aNV3-)*~y&Gn$KEGQ3m=s7629i4*&qj0B=M@2OXXP02QzR044w)>aCca zt&@qZlfJ6Ey$MK{+0Dk9^uu!~+8h8B}|A&8p;lv@^PF6IDtHfK>*m^ai98@)I ze+iNyoT78+B{xwO-=EN{InW1_Eyt6vI+c2F?PA%ir*u8xL$2SDX%}+RK-SJ}2J> zQTR6ITLnk(z5}Wcu45aruI!B;+}l(fdFD;HQIX*zkmyOs;d$lWBwtJ^SQX6M#^Ol7 zO)3UhgloxR_K&-xVKDe9?lYEYF(u5~AD=|`^lwfIu)R6+QMwk6T1mCn$wufKNwr^k z#C)B@4!opW3SF$o;Nv&qu1VX>|ZS}byTwLal-0LU1+~6z?$H3yFsB`@cPQG<;hNGu;Op}bo3ZpDX zUUGMRW9u(x;_2;}4_-|28U4!SoEX>aW3T(DaxQ$eYdBAbGTpnm5xPqHPX+?QNeWjW zN$cN5BB1H#fd={H=s5uJ0s#Na&6?#OI&rmgv@)`@v-+9P{;8Q~kgNu|_W$Ye=ObwIsJ|is~AncuwC&Kv~!Oo9qo z>4}yK2bMRoa=*i|z_D$r`(wFDd3bo4h^bUn@?$x;-z;sSNVPLt`cep0fbXgNu(+6R zZ2~elk%a|<1$s~pId9^+U_?oVRGr2Nh^M&!;XTpAFtbP}hlta{rj0fc# zZU_zjolo1gn~+CN z;}2Q1W`AIDEg~kDd!w_{V9b^J;Whbq@cgQ;97@Eh9e(pHAO}Z9_04=Yn5-&5Ra2)D zJY7-!uB8*h_GSeQNb?F8hOCMnxX9_9*I6SeAUeDn(BZ}BM#6GF&|cXH;aoz5 zfGtOD{_Pt5@bRMLnC1+iJ1I$3&?Vs(j5!TyFC z_Q)$tYnpPv!mgD5ZoO<*m0tduTWVlEStBeBs%qwXf7soz@piQ4BS(tKBU>8G*S!`D zu^%g;-y)Z+Inxi!rP?mP&e%_R^(OKbVOUV%I3}t{{h&i!OCstIhgo?Uo|IH!l^%6S z_U`4v4cLlje!VWi)nMxxF@4uno*+g98w=GsSCa97C4wOT_gj^*5o*6yM!Z)#%~V3# zg9unS*GM&;A=P+xJ;0=(uRk2al1^{K0XJ4U-}VVc+u~)zoI`o~qPr+X8Z>t_H!f23 zPfZ-|;rcdYsfO_yFK5d^Pzmn*4L>9ufo0|K(e~_58*r~^?8SWOH?3ZU*q_y;u{=-$ zp_Ub(-nu@R7X>RdO~?1wwg%zDm>NGm$6ku&0Ke7B7}YbBkKKGU-NzI};Y-4==k)b? zig&-=we;aa_7qA~p4o0wc7!aLO7#n;1fM}JyZQWCx+G28cAUvDc*8%SViw9D#CF>N zWl7sN#FO^a-=Q137U}l-7sJ-E1cIc{7S{PEcKkVJb@}`lSZbYxfE0b8B*Q%qB96@n z?O6?fBW^2a4J4pdj%NwDMA@1wvTqOOp0_@s;%uC0yUOo~M^em}An$N?g6A1-)@weq ziP8Pt5m=JYIqT7Jk>ijfz20LUtMp_A;kI6yrZ6tp9(-mF+P7TJ?1B9?eBN8MLWn%+ zUy!lI$f}d9Mmr?SY|980T)X+KveV5~HE3!lQXwOVZfBblAN z^8^*Vkzd4Bmy3UEQI{O3VrF>8ZcIXL1vRV9X%hei|5e?bsd!!Wi&cyWb1A}bIzn!+ zSo-{FPul$-c^1UYE%qJ4vkpjt_zyf||HZQ;{pb&@SUs!U^T)z&8Vkyg_|6|TjCTy< zM@(H;8E~ke6Kd4Vs^*W|a230Gsd+=GHyQ=*7d#tyeLNT(t;RkMit|*SqRr#5U{;O} zj&wgh?7O2aBu$p;RO=*o8MT82}C0r@hJI0Nz`stL<&6 zQ(nzs%eEaAi;#yQGOBB3eZT6Rt_w3huc8YwE+M>p0Mp5oawD)n*X?r8%ANu8zl|LuGb}$NH{-a9H}x@7CrKteth(ra z3?E?BW^S=ZV{E?g9>5Wsde>Cj5jLjK6CWfV>S&0 z2;frO@?N_+fwrA8rfWHVGOl?+VPb9`THA5@n>SG35^1TF z6uvoGbP3^HS<@w876Ap#b#DCYU1e@V7d5PlKF2Oje#d1I0Ukk^0wPKT*HE4)|5MAi z077;rAs(z%%44hEQ>LXPLd+{KA33DWyZ}+l1B-cjJ*@L4ClPrPvfz!VC4-bJuCrka zG5Jc|Ysr$0cr4g%VBHFLi7-&$b* z2zhc?czC^*+Yl&p$F-ad!P$Di$&w^v%R0QKz40;sX5pR?5Cgm3chW&|qUeR$R-Q^j zQggyg#>s9vtn`5_EJD14w2UToUxJpG`{RfgA81iYs}lG$x<$>9n0Qu`S^!!9{KLUq zDTRA<1j4-PqE<>7WpyVS{#^&+Vs3gWWar;MWbH3!M}PPIqkp3C=sMf^|I35pAM#j? zT3|&3A2VDKeOMAR>f<3ZfO$UMTBxdKb1Y|1x|6$?H0=j4xnRvo4BG^J%!6@jFw6#rw~pp58v;L({>;A;@jL)y+Fremh1q>OqjN6E+Ea$GinU^ ze)DRQ$?cpeLhgu5Z4{2LwbMScSQheCc1dbom7IxsJGx50j$Kz-qPS1(qEf$!YIvY2 z8l8GOFt)1MBt*OGjZ0cWui!$09?w>TI~P&8X!AlzVn?N83^oC=qm|ZT=GxrRb=&KO zMkNwFEC)gX@5o`1GgM8(Op-ScSc_e(VL-qmMz;5y9qVi6no9soH_^+43kPfXql!xK z@+@Z)c3Gt^O$~Hpvs%o4@QZ7c$Q_+{R?=W%j|{?qCU1rUGFOC4&L8RyG39jo z>ZhVkOox6M{9-5_L^riyX9A|Xv@FnMjr3YPv1BV_)0;Vj8rZk>%Dao#+QrZK&o4JE zg4dc)yyX;{7d!LDXGRHYg9(_Pa-tKW#2f+)4%RAmZ5m&$)x`mpIgQf94MFRpTe* zT(uf0u?tIyWDb}<-RTLV?WeqoHz&_poC0~++NB7#m=-z5G|8$8R-fX{Ck<27O8qz- zhU5;5twnXMP%jGQ#G%v&YMDF3$BUu!!gHC%_^K*DG8VJTvf{AN&4dYg(+`9tqTl?w zrOiGByl!V=}Sjp3cDZmG0W7r!1sY14sg;zhE8QBZm;5*iGV`ZrEt;d$Y>gFjnM)n-8=XmVfBELE<(9X=r`f=b4DTSGS;E# z*v}0;5Z(){=|Pnc8wjlVZRox1_Gq`JJu<-*yF}xdW|E>v7a0{iL*6FBQ6$F8D8cLv z3Fm+Eei)a!+7I4HzYR`t2$5OXyW$6*YQ4rZnKKl8UFQE;9I0fLyQ#Rq)roKFiP8q{> z*^^JNSfHLlU})Pc3`0`sPI?2vD`j4B!)na=Kd1Qif;3{!<7;Dz$MYMr3c1|Op~F{9 z-ReHL9_d>j69*eo8%d?=#qcAo+=~lo*n)kuN{W@Wu4)2j5fIL>BiCQ{Nvkc33EbV` zCfxivo0tv^bF4t}OcBJ2Vg19@gUk&bO^j8X94&0kKtILe6w@Ky{WFsU9HV_Z`lPEU z9#`5>k=62DE@phV&fj=)BtP8SGtYDX>!#N#$+zsnWmn{lh*WQK;Nhod{?k-DMYAWT zJ`JAFJ9^@($7c!o#6H6vsE+HQy$NS-c*YmHn<3MX9oPZmv9rsoU8RUA;wHZydvNmQNzf5VInLt=u zcc7%s!5-H_fC)v#bD>O||1nwAP9R}Y7#i4m9*sa*^POaZW@L`y^EPF=wY0ViF#Ppn zYeB(PmXF7o)9GYuipT2|q|Ue`{WirP0vQo=DU?%5zE-bjIlUC#Bt)Q_AlTj@G2Y88 zX8Smz+YG?>%|YLYTjwT`NbxMv(Qz)N!i^y7PnP5ptk2nedt1+ zzKi__?e@CKf@kKO0g0dL=6LrLZDS09inlYl%^JsQLpUqr`nuJPM8(lTvpK516<`wN zrZowqauGT~ovz?a(*m>o*&HnWw_UR`hkAW1t6FVI01yA)J}D11juS@Tbtya=>W9*M zOS;$TTyfaBNT}Hh)570wHQz*X+DI9dS}s`$v-B>)1<#dttvGGymg_g;p~HUd9xLknh3j6H>I@J7mtRbfC;5&c;R5 z7H+)7?&#a`n!1dIQrshq?(Fs*<9nHJyulYQMA{LNH&O10b-Q22$vEy#HiqMI#0t2~ zMJn=x7G&f4yM3rp$RuK21Ep}iqUZU<1;hvytJs$65$ZZ0OvxKywk*>UXhLpHMu{q0 zB2rsPaGe4w!c7+V>*i`lLOJ+&&x#Tkn(jgoYGnibRCfHP%**OHb;n0Snujb#p5MP$oeYKsT>X8|RBDcl` zwN0`WK1DYRv&$OS+%B~r39n_A?16LSrEAaNV^`g0puZw;*!CRX%MzdTvi@=eP01>N z-w+Ish>W`&5Q6=vBc55JZGDuGK9~Ly6&g!Yy%jTlE3#)Sw2J25b&%Ng83-08`t9jm z0&hC4$&R>xHNq7Mqm_qw*3uXIxKhwpcQugS%=Ob>rDJ zR*6a}=LtJoNg|_Lp?P<>(epJmSs37=LjA%69rs4q{L?G2W4CWyHw*^NXIsK1c@m;1 zc*7N4KBO3Db#jH44NoXAkl-oIr*-7+GxMhuLtnNkEOV3041!=SzL|aMq2HT!ivJ$W zw*@TuAvRq_|IzkAp2@Myin$R5W5!*&WqsN^9J+&*DSMb*POC+}E3CR&X|aZb5gu7# z`sKBav*as*(=r;N{>))*XJN*D>CEff!KIcNQ#eWE8|4Aza5M^P;R&k#jvDUHvExaX_iBFzZAzY#~UtvAL zL(T&!!#jg*I&b4Pz?Efl-P0e_@4V8A7Y@X~PePha|DpE7^~=8}EJi_UjK4OwjH`h> z+wc9omQ{fT(v_a zE?uN;X;4W2!L!GL&DxRx!)+y_M%BrUvU`sh%qY|0#yp?9orzBt#yu_k*o4#CHmU7` zTK3V>cbABIkr<6{0v?2)gICMwQ^s4gTjt+1<|B^J%isp()>Sgh>YF!>_$fc>ql3R} zZ-(8Fek^vbHTm*aU)6#r74?t=y*iouWFBn4Jkw zn5&RmSL%As`a9$39r?jl-D_l(tYvB2OWWYh8@b$oGCy7hP-u13xLF@r@8^T-r_@f;1Ds-gO$%(WL;6WR=d6UBXKzCO=DDIrGpa zb4#P?)OT?!cjVDqA1Bmjj}Yxc%dGUOlAU&p<;~qCp|)npBoGib!x#9@r1KHjtovHM zUMX08&XD+Mn+VP3Uuf5{q4oW^_$0dp6f&rC2YpxwPp$At|CpE`7)62GD`1IEMsfj^O!QL&#=-y1j)e&+Y@T7UnORYEe~-nC-9-hjikfrGEHZM4^U@MkqpUv7lS)%bE#GRwV_0K=ZD-;YcH8N?aDj#|xQMD(WWEQg7!}aOW6@Nyas&IWFbNH7xJnQXcWyJL=u}gt z&Wby$03K9|7gDfw>fo{TjR;`w9HjGR)5lmAtgSCzdEd&i7YNuKCC zmXPO*?N}mF(lKY*B98=rqO&EuYM2;g(KLfl8XeMyH@17H=xApTVtHrhX!3JSgp92G zxBU*$c|@F&LKi7k(5l=$D)5-e3Dgu@7v6L*Kt(-j?nPl=kB&s;=W=&7=~ANmEiifs zbp2`vY~7||at{1DAiYB?2OlPUCjF*$WMba~UqNY$e<~m`5ll$*CD+}-JJCVR-DG`U z9kw)i?St@Slw*8e<`vlJ;2H%BBxS!#YD-^|g8I6-2Yjkf2g6Ukg~MMjtaXeSp9~W#DXT#ekPPIQ*L+oHGA7ylaN+!toKl!&srM`^tL=1d<90l)vM%fZqJI4u`?`Th(p~cy9^#hep?2ZN@>ifFRz2Rto>o+}(ev_-TAT>zD5wvx+ z4r#7wFu09TgLVm?sowcK-d-ELE;evhBJoJ2FlIi9dB45i|5y3z859$w)b;0S-GBa- z|2+R;c2`m6UjhDgi143*KhMPwz5H#Y@b|!f9jy9$;26ZQ{O2*N-*J8)sQC+t1mWMt zYkm*@y{Z3K@Er302LD@&|96z%Teg3p2t!7E{%6AH?xq94{a45FS0DhOK@9->oB#Md{9n_?-@_+p c{}%o~NkmZw2Euv(00r_$fe5FR;peOW2dqF%+yDRo diff --git a/larray/tests/data/population_session/__axes__.csv b/larray/tests/data/population_session/__axes__.csv deleted file mode 100644 index a05472fe4..000000000 --- a/larray/tests/data/population_session/__axes__.csv +++ /dev/null @@ -1,4 +0,0 @@ -country,gender,time -Belgium,Male,2013 -France,Female,2014 -Germany,,2015 diff --git a/larray/tests/data/population_session/births.csv b/larray/tests/data/population_session/births.csv deleted file mode 100644 index e00b6f566..000000000 --- a/larray/tests/data/population_session/births.csv +++ /dev/null @@ -1,7 +0,0 @@ -country,gender\time,2013,2014,2015 -Belgium,Male,64371,64173,62561 -Belgium,Female,61235,60841,59713 -France,Male,415762,418721,409145 -France,Female,396581,400607,390526 -Germany,Male,349820,366835,378478 -Germany,Female,332249,348092,359097 diff --git a/larray/tests/data/population_session/deaths.csv b/larray/tests/data/population_session/deaths.csv deleted file mode 100644 index 5bc6ece8c..000000000 --- a/larray/tests/data/population_session/deaths.csv +++ /dev/null @@ -1,7 +0,0 @@ -country,gender\time,2013,2014,2015 -Belgium,Male,53908,51579,53631 -Belgium,Female,55426,53176,56910 -France,Male,287410,282381,297028 -France,Female,281955,277054,296779 -Germany,Male,429645,422225,449512 -Germany,Female,464180,446131,475688 diff --git a/larray/tests/data/population_session/pop.csv b/larray/tests/data/population_session/pop.csv deleted file mode 100644 index 4bc913d7d..000000000 --- a/larray/tests/data/population_session/pop.csv +++ /dev/null @@ -1,7 +0,0 @@ -country,gender\time,2013,2014,2015 -Belgium,Male,5472856,5493792,5524068 -Belgium,Female,5665118,5687048,5713206 -France,Male,31772665,31936596,32175328 -France,Female,33827685,34005671,34280951 -Germany,Male,39380976,39556923,39835457 -Germany,Female,41142770,41210540,41362080 diff --git a/larray/tests/data/test.xlsx b/larray/tests/data/test.xlsx index 61b849a2610824ef287ad98353cc66438aee2f76..6d9b4e2d90ff48b136c8454fbacbd69e82cb1984 100644 GIT binary patch delta 9433 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 'even_years' + odd_years = pop.time[2013::2] >> 'odd_years' + + # Session + ses = Session({'country': pop.country, 'country_benelux': immigration.country, + 'citizenship': immigration.citizenship, + 'gender': pop.gender, 'time': pop.time, + 'even_years': even_years, 'odd_years': odd_years, + 'pop': pop, 'pop_benelux': pop_benelux, 'births': births, 'deaths': deaths, + 'immigration': immigration}) + ses.meta.title = 'Demographic datasets for a small selection of countries in Europe' + ses.meta.source = 'demo_jpan, demo_fasec, demo_magec and migr_imm1ctz tables from Eurostat' + + # EUROSTAT DATASET + + if csv: + ses.save(os.path.join(DATA_DIR, 'demography_eurostat')) + if excel: + ses.save(os.path.join(DATA_DIR, 'demography_eurostat.xlsx')) + if hdf5: + ses.save(os.path.join(DATA_DIR, 'demography_eurostat.h5')) + + # EXAMPLE FILES + + years = pop.time[2013:2015] + pop = pop[years] + pop_narrow = pop['Belgium,France'].sum('gender') + births = births[years] + deaths = deaths[years] + immigration = immigration[years] + + # Dataframes (for testing missing axis/values) + df_missing_axis_name = pop.to_frame(fold_last_axis_name=False) + df_missing_values = pop.to_frame(fold_last_axis_name=True) + df_missing_values.drop([('France', 'Male'), ('Germany', 'Female')], inplace=True) + + if csv: + examples_dir = os.path.join(DATA_DIR, 'examples') + pop.to_csv(os.path.join(examples_dir, 'pop.csv')) + births.to_csv(os.path.join(examples_dir, 'births.csv')) + deaths.to_csv(os.path.join(examples_dir, 'deaths.csv')) + immigration.to_csv(os.path.join(examples_dir, 'immigration.csv')) + df_missing_axis_name.to_csv(os.path.join(examples_dir, 'pop_missing_axis_name.csv'), sep=',', na_rep='') + df_missing_values.to_csv(os.path.join(examples_dir, 'pop_missing_values.csv'), sep=',', na_rep='') + pop_narrow.to_csv(os.path.join(examples_dir, 'pop_narrow_format.csv'), wide=False) + + if excel: + with open_excel(os.path.join(DATA_DIR, 'examples.xlsx'), overwrite_file=True) as wb: + wb['pop'] = pop.dump() + wb['births'] = births.dump() + wb['deaths'] = deaths.dump() + wb['immigration'] = immigration.dump() + wb['pop_births_deaths'] = pop.dump() + wb['pop_births_deaths']['A9'] = births.dump() + wb['pop_births_deaths']['A17'] = deaths.dump() + wb['pop_missing_axis_name'] = '' + wb['pop_missing_axis_name']['A1'].options().value = df_missing_axis_name + wb['pop_missing_values'] = '' + wb['pop_missing_values']['A1'].options().value = df_missing_values + # wb['pop_narrow_format'] = pop_narrow.dump(wide=False) + wb.save() + pop_narrow.to_excel(os.path.join(DATA_DIR, 'examples.xlsx'), 'pop_narrow_format', wide=False) + Session({'country': pop.country, 'gender': pop.gender, 'time': pop.time, + 'pop': pop}).save(os.path.join(DATA_DIR, 'pop_only.xlsx')) + Session({'births': births, 'deaths': deaths}).save(os.path.join(DATA_DIR, 'births_and_deaths.xlsx')) + + if hdf5: + examples_h5_file = os.path.join(DATA_DIR, 'examples.h5') + pop.to_hdf(examples_h5_file, 'pop') + births.to_hdf(examples_h5_file, 'births') + deaths.to_hdf(examples_h5_file, 'deaths') + immigration.to_hdf(examples_h5_file, 'immigration') + + +if __name__ == '__main__': + # generate_tests_files() + generate_example_files() diff --git a/larray/tests/test_excel.py b/larray/tests/test_excel.py index 06b991931..c5ec86221 100644 --- a/larray/tests/test_excel.py +++ b/larray/tests/test_excel.py @@ -338,7 +338,7 @@ def test_excel_report_titles(): @needs_xlwings def test_excel_report_arrays(): excel_report = ExcelReport(EXAMPLE_EXCEL_TEMPLATES_DIR) - demo = load_example_data('demo') + demo = load_example_data('demography_eurostat') pop = demo.pop pop_be = pop['Belgium'] pop_be_nan = pop_be.astype(float)