<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Working With Sessions
Import the LArray library
Step1:  Three Kinds Of Sessions
They are three ways to group objects in LArray
Step2:  CheckedSession
The syntax to define a checked-session is a bit specific
Step3:  Loading and Dumping Sessions
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. 
Loading Sessions (CSV, Excel, HDF5)
To load the items of a session, you have two options
Step4:  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
Step5:  The load method offers some options
Step6:  2) Setting the display argument to True, the load method will print a message each time a new item is loaded
Step7:  Dumping Sessions (CSV, Excel, HDF5)
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
Step8:  <div class="alert alert-info">
  Note
Step9:  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
Step10:  3) Setting the display argument to True, the save method will print a message each time an item is dumped
Step11:  Exploring Content
To get the list of items names of a session, use the names shortcut (be careful that the list is sorted alphabetically and does not follow the internal order!)
Step12:  To get more information of items of a session, the summary  will provide not only the names of items but also the list of labels in the case of axes or groups and the list of axes, the shape and the dtype in the case of arrays
Step13:  Selecting And Filtering Items
Session objects work like ordinary dict Python objects. To select an item, use the usual syntax &lt;session_var&gt;['&lt;item_name&gt;']
Step14:  A simpler way consists in the use the syntax &lt;session_var&gt;.&lt;item_name&gt;
Step15:  <div class="alert alert-warning">
    **Warning
Step16:  <div class="alert alert-warning">
    **Warning
Step17:  The filter method allows you to select all items of the same kind (i.e. all axes, or groups or arrays) or all items with names satisfying a given pattern
Step18:  <div class="alert alert-warning">
    **Warning
Step19:  Iterating over Items
Like the built-in Python dict objects, Session objects provide methods to iterate over items
Step20:  Manipulating Checked Sessions
Note
Step21:  One of the specificities of checked-sessions is that the type of the contained objects is protected (it cannot change). Any attempt to assign a value of different type will raise an error
Step22:  The death array has been declared as a CheckedArray. 
As a consequence, its axes are protected. 
Trying to assign a value with incompatible axes raises an error
Step23:  The deaths array is also constrained by its declared dtype int. This means that if you try to assign a value of type float instead of int, the value will be converted to int if possible
Step24:  or raise an error
Step25:  It is possible to add a new variable after the checked-session has been initialized but in that case, a warning message is printed (in case you misspelled the name of variable while trying to modify it)
Step26:  Arithmetic Operations On Sessions
Session objects accept binary operations with a scalar
Step27:  with an array (please read the documentation of the random.choice function first if you don't know it)
Step28:  with another session
Step29:  Applying Functions On All Arrays
In addition to the classical arithmetic operations, the apply method can be used to apply the same function on all arrays. This function should take a single element argument and return a single value
Step30:  It is possible to pass a function with additional arguments
Step31:  It is also possible to apply a function on non-Array objects of a session. Please refer the documentation of the apply method.
Comparing Sessions
Being able to compare two sessions may be useful when you want to compare two different models expected to give the same results or when you have updated your model and want to see what are the consequences of the recent changes.
Session objects provide the two methods to compare two sessions
Step32:  The == operator return a new session with boolean arrays with elements compared element-wise
Step33:  This also works for axes and groups
Step34:  The != operator does the opposite of == operator
<ASSISTANT_TASK:>
Python Code:
%xmode Minimal
from larray import *
Explanation: Working With Sessions
Import the LArray library:
End of explanation
# define some scalars, axes and arrays
variant = 'baseline'
country = Axis('country=Belgium,France,Germany')
gender = Axis('gender=Male,Female')
time = Axis('time=2013..2017')
population = zeros([country, gender, time])
births = zeros([country, gender, time])
deaths = zeros([country, gender, time])
# create an empty session and objects one by one after
s = Session()
s.variant = variant
s.country = country
s.gender = gender
s.time = time
s.population = population
s.births = births
s.deaths = deaths
print(s.summary())
# or create a session in one step by passing all objects to the constructor
s = Session(variant=variant, country=country, gender=gender, time=time, 
            population=population, births=births, deaths=deaths)
print(s.summary())
Explanation: Three Kinds Of Sessions
They are three ways to group objects in LArray:
Session: is an ordered dict-like container with special I/O methods. Although the autocomplete* feature on the objects stored in the session is available in the larray-editor, it is not available in development tools like PyCharm making it cumbersome to use.
CheckedSession: provides the same methods as Session objects but are defined in a completely different way (see example below). The autocomplete* feature is both available in the larray-editor and in development tools (PyCharm). In addition, the type of each stored object is protected. Optionally, it is possible to constrain the axes and dtype of arrays using CheckedArray.
CheckedParameters: is a special version of CheckedSession in which the value of all stored objects (parameters) is frozen after initialization.
* Autocomplete is the feature in which development tools try to predict the variable or function a user intends to enter after only a few characters have been typed (like word completion in cell phones).
Creating Sessions
Session
Create a session:
End of explanation
class Demography(CheckedSession):
    # (convention is to declare parameters (read-only objects) in capital letters)
    # Declare 'VARIANT' parameter as of type string.
    # 'VARIANT' will be initialized when a 'Demography' session will be created
    VARIANT: str
    # declare variables with an initialization value.
    # Their type is deduced from their initialization value. 
    COUNTRY = Axis('country=Belgium,France,Germany')
    GENDER = Axis('gender=Male,Female')
    TIME = Axis('time=2013..2017')
    population = zeros([COUNTRY, GENDER, TIME], dtype=int)
    births = zeros([COUNTRY, GENDER, TIME], dtype=int)
    # declare 'deaths' with constrained axes and dtype.
    # Its type (Array), axes and dtype are not modifiable.
    # It will be initialized with 0
    deaths: CheckedArray([COUNTRY, GENDER, TIME], int) = 0
d = Demography(VARIANT='baseline')
print(d.summary())
Explanation: CheckedSession
The syntax to define a checked-session is a bit specific:
python
class MySession(CheckedSession):
    # Variables can be declared in two ways:
    # a) by specifying only the type of the variable (to be initialized later)
    var1: Type
    # b) by giving an initialization value.
    #    In that case, the type is deduced from the initialization value
    var2 = initialization value
    # Additionally, axes and dtype of Array variables can be constrained 
    # using the special type CheckedArray
    arr1: CheckedArray([list, of, axes], dtype) = initialization value
Check the example below:
End of explanation
# 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('demography_eurostat')
s = 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('demography_eurostat.xlsx')
s = 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('demography_eurostat.h5')
s = Session(filepath_hdf)
print(s.summary())
Explanation: Loading and Dumping Sessions
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. 
Loading Sessions (CSV, Excel, HDF5)
To load the items of a session, you have two options:
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:
End of explanation
# create a session containing 3 axes, 2 groups and one array 'population'
filepath = get_example_filepath('population_only.xlsx')
s = Session(filepath)
print(s.summary())
# call the load method on the previous session and add the 'births' and 'deaths' arrays to it
filepath = get_example_filepath('births_and_deaths.xlsx')
s.load(filepath)
print(s.summary())
Explanation: 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:
End of explanation
births_and_deaths_session = Session()
# use the names argument to only load births and deaths arrays
births_and_deaths_session.load(filepath_hdf, names=['births', 'deaths'])
print(births_and_deaths_session.summary())
Explanation: The load method offers some options:
1) Using the names argument, you can specify which items to load:
End of explanation
s = Session()
# with display=True, the load method will print a message
# each time a new item is loaded
s.load(filepath_hdf, display=True)
Explanation: 2) Setting the display argument to True, the load method will print a message each time a new item is loaded:
End of explanation
# save items of a session in CSV files.
# Here, the save method will create a 'demography' directory in which CSV files will be written 
s.save('demography')
# save the session to an HDF5 file
s.save('demography.h5')
# save the session to an Excel file
s.save('demography.xlsx')
Explanation: Dumping Sessions (CSV, Excel, HDF5)
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:
End of explanation
# use the names argument to only save births and deaths arrays
s.save('demography.h5', names=['births', 'deaths'])
# load session saved in 'demography.h5' to see its content
Session('demography.h5').names
Explanation: <div class="alert alert-info">
  Note: Concerning the CSV and Excel formats, the metadata is saved in one Excel sheet (CSV file) named `__metadata__(.csv)`. This sheet (CSV file) name cannot be changed. 
</div>
The save method has several arguments:
1) Using the names argument, you can specify which items to save:
End of explanation
population = read_csv('./demography/population.csv')
pop_ses = Session([('population', population)])
# by setting overwrite to False, the destination file is updated instead of overwritten.
# The items already stored in the file but not present in the session are left intact. 
# On the contrary, the items that exist in both the file and the session are completely overwritten.
pop_ses.save('demography.h5', overwrite=False)
# load session saved in 'demography.h5' to see its content
Session('demography.h5').names
Explanation: 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:
End of explanation
# with display=True, the save method will print a message
# each time an item is dumped
s.save('demography.h5', display=True)
Explanation: 3) Setting the display argument to True, the save method will print a message each time an item is dumped:
End of explanation
# load a session representing the results of a demographic model
filepath_hdf = get_example_filepath('demography_eurostat.h5')
s = Session(filepath_hdf)
# print the content of the session
print(s.names)
Explanation: Exploring Content
To get the list of items names of a session, use the names shortcut (be careful that the list is sorted alphabetically and does not follow the internal order!):
End of explanation
# print the content of the session
print(s.summary())
Explanation: To get more information of items of a session, the summary  will provide not only the names of items but also the list of labels in the case of axes or groups and the list of axes, the shape and the dtype in the case of arrays:
End of explanation
s['population']
Explanation: Selecting And Filtering Items
Session objects work like ordinary dict Python objects. To select an item, use the usual syntax &lt;session_var&gt;['&lt;item_name&gt;']:
End of explanation
s.population
Explanation: A simpler way consists in the use the syntax &lt;session_var&gt;.&lt;item_name&gt;:
End of explanation
s_selected = s['population', 'births', 'deaths']
s_selected.names
Explanation: <div class="alert alert-warning">
    **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.
</div>
To return a new session with selected items, use the syntax &lt;session_var&gt;[list, of, item, names]:
End of explanation
d_selected = d['births', 'deaths']
# test if v_selected is a checked-session
print('is still a check-session?', isinstance(d_selected, CheckedSession))
#test if v_selected is a normal session
print('is now a normal session?', isinstance(d_selected, Session))
Explanation: <div class="alert alert-warning">
    **Warning:** The same selection as above can be applied on a checked-session **but the returned object is a normal session and NOT a checked-session**. This means that you will loose all the benefits (autocomplete, protection on type, axes and dtype) of checked-sessions. 
</div>
End of explanation
# select only arrays of a session
s.filter(kind=Array)
# selection all items with a name starting with a letter between a and k
s.filter(pattern='[a-k]*')
Explanation: The filter method allows you to select all items of the same kind (i.e. all axes, or groups or arrays) or all items with names satisfying a given pattern:
End of explanation
d_filtered = d.filter(pattern='[a-k]*')
# test if v_selected is a checked-session
print('is still a check-session?', isinstance(d_filtered, CheckedSession))
#test if v_selected is a normal session
print('is now a normal session?', isinstance(d_filtered, Session))
Explanation: <div class="alert alert-warning">
    **Warning:** Using the *filter()* method on a checked-session **will return a normal session and NOT a checked-session**. This means that you will loose all the benefits (autocomplete, protection on type, axes and dtype) of checked-sessions. 
</div>
End of explanation
# iterate over item names
for key in s.keys():
    print(key)
# iterate over items
for value in s.values():
    if isinstance(value, Array):
        print(value.info)
    else:
        print(repr(value))
    print()
# iterate over names and items
for key, value in s.items():
    if isinstance(value, Array):
        print(key, ':')
        print(value.info)
    else:
        print(key, ':', repr(value))
    print()
Explanation: Iterating over Items
Like the built-in Python dict objects, Session objects provide methods to iterate over items:
End of explanation
class Demography(CheckedSession):
    COUNTRY = Axis('country=Belgium,France,Germany')
    GENDER = Axis('gender=Male,Female')
    TIME = Axis('time=2013..2017')
    population = zeros([COUNTRY, GENDER, TIME], dtype=int)
    # declare the deaths array with constrained axes and dtype
    deaths: CheckedArray([COUNTRY, GENDER, TIME], int) = 0
d = Demography()
print(d.summary())
Explanation: Manipulating Checked Sessions
Note: this section only concerns objects declared in checked-sessions.
Let's create a simplified version of the Demography checked-session we have defined above:
End of explanation
# The population variable was initialized with the zeros() function which returns an Array object.
# The declared type of the population variable is Array and is protected 
d.population = Axis('population=child,teenager,adult,elderly')
Explanation: One of the specificities of checked-sessions is that the type of the contained objects is protected (it cannot change). Any attempt to assign a value of different type will raise an error:
End of explanation
AGE = Axis('age=0..100')
d.deaths = zeros([d.COUNTRY, AGE, d.GENDER, d.TIME])
Explanation: The death array has been declared as a CheckedArray. 
As a consequence, its axes are protected. 
Trying to assign a value with incompatible axes raises an error:
End of explanation
d.deaths = 1.2
d.deaths
Explanation: The deaths array is also constrained by its declared dtype int. This means that if you try to assign a value of type float instead of int, the value will be converted to int if possible:
End of explanation
d.deaths = 'undead'
Explanation: or raise an error:
End of explanation
# misspell population (forgot the 'a')
d.popultion = 0
Explanation: It is possible to add a new variable after the checked-session has been initialized but in that case, a warning message is printed (in case you misspelled the name of variable while trying to modify it):
End of explanation
# get population, births and deaths in millions
s_div = s / 1e6
s_div.population
Explanation: Arithmetic Operations On Sessions
Session objects accept binary operations with a scalar:
End of explanation
from larray import random
random_increment = random.choice([-1, 0, 1], p=[0.3, 0.4, 0.3], axes=s.population.axes) * 1000
random_increment
# add some variables of a session by a common array
s_rand = s['population', 'births', 'deaths'] + random_increment
s_rand.population
Explanation: with an array (please read the documentation of the random.choice function first if you don't know it):
End of explanation
# compute the difference between each array of the two sessions
s_diff = s - s_rand
s_diff.births
Explanation: with another session:
End of explanation
# 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_with_next_year = s.apply(add_next_year)
print('population array before calling apply:')
print(s.population)
print()
print('population array after calling apply:')
print(s_with_next_year.population)
Explanation: Applying Functions On All Arrays
In addition to the classical arithmetic operations, the apply method can be used to apply the same function on all arrays. This function should take a single element argument and return a single value:
End of explanation
# add the next year to all arrays.
# Use the 'copy_values_from_last_year flag' to indicate 
# whether 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_with_next_year = s.apply(add_next_year, True)
print('population array before calling apply:')
print(s.population)
print()
print('population array after calling apply:')
print(s_with_next_year.population)
Explanation: It is possible to pass a function with additional arguments:
End of explanation
# load a session representing the results of a demographic model
filepath_hdf = get_example_filepath('demography_eurostat.h5')
s = Session(filepath_hdf)
# create a copy of the original session
s_copy = s.copy()
# 'element_equals' compare arrays one by one
s.element_equals(s_copy)
# 'equals' returns True if all items of the two sessions have exactly the same items
s.equals(s_copy)
# slightly modify the 'population' array for some labels combination
s_copy.population += random_increment 
# the 'population' array is different between the two sessions
s.element_equals(s_copy)
# 'equals' returns False if at least one item of the two sessions are different in values or axes
s.equals(s_copy)
# reset the 'copy' session as a copy of the original session
s_copy = s.copy()
# add an array to the 'copy' session
s_copy.gender_ratio = s_copy.population.ratio('gender')
# the 'gender_ratio' array is not present in the original session
s.element_equals(s_copy)
# 'equals' returns False if at least one item is not present in the two sessions
s.equals(s_copy)
Explanation: It is also possible to apply a function on non-Array objects of a session. Please refer the documentation of the apply method.
Comparing Sessions
Being able to compare two sessions may be useful when you want to compare two different models expected to give the same results or when you have updated your model and want to see what are the consequences of the recent changes.
Session objects provide the two methods to compare two sessions: equals and element_equals:
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.
End of explanation
# reset the 'copy' session as a copy of the original session
s_copy = s.copy()
# slightly modify the 'population' array for some labels combination
s_copy.population += random_increment
s_check_same_values = s == s_copy
s_check_same_values.population
Explanation: The == operator return a new session with boolean arrays with elements compared element-wise:
End of explanation
s_check_same_values.time
Explanation: This also works for axes and groups:
End of explanation
s_check_different_values = s != s_copy
s_check_different_values.population
Explanation: The != operator does the opposite of == operator:
End of explanation
<END_TASK>