Skip to content

Latest commit

 

History

History
750 lines (494 loc) · 22.7 KB

File metadata and controls

750 lines (494 loc) · 22.7 KB

Getting started

.. currentmodule:: psyplot.project


Initialization and interactive usage

This section shall introduce you how to read data from a netCDF file and visualize it via psyplot. For this, you need to have netCDF4 and the psy-maps psyplot plugin to be installed (see :ref:`install`).

Furthermore we use the :download:`demo.nc` netCDF file for our demonstrations.

Note

We recommend to either run this example using our :ref:`GUI <psyplot_gui:psyplot-gui>`. However, you can also either use IPython from the terminal via

conda install ipython  # or pip install ipython
ipython  # starts the ipython console

and copy-paste the commands in this example, or you use a jupyter notebook via

conda install jupyter  # or pip install jupyter
jupyter notebook  # starts the notebook server

Then create a new notebook in the desired location and copy-paste the examples below. If you want, we also recommend to include the following commands in the notebook

import psyplot.project as psy
# show the figures inline in the notebook and not in a separate window
%matplotlib inline
# don't close the figures after showing them, because than the update
# would not work
%config InlineBackend.close_figures = False
# show the figures after they are drawn or updated. This is useful
# for the visualization in the jupyter notebook
psy.rcParams['auto_show'] = True

After you :ref:`installed psyplot <install>`, you can import the package via

.. ipython::

    In [1]: import psyplot

Psyplot has several modules and subpackages. The main module for the use of psyplot is the :mod:`~psyplot.project` module.

.. ipython::

    In [2]: import psyplot.project as psy

Plots can be created using the attributes of the :attr:`plot` instance of the :class:`ProjectPlotter`.

Each new plugin defines several plot methods. In case of the psy-maps package, those are

.. ipython::

    In [3]: psy.plot.show_plot_methods()

So to create a simple 2D plot of the temperature field 't2m', you can type

.. ipython::

    @savefig docs_getting_started.png width=4in
    In [4]: p = psy.plot.mapplot('demo.nc', name='t2m')

Note

If you're not using the :ref:`GUI <psyplot_gui:psyplot-gui>`, you have to call the :meth:`~Project.show` method to display the plot, i.e. just run

p.show()

Now you created your first project

.. ipython::

    In [5]: p

which contains the :class:`xarray.DataArray` that stores the data and the corresponding plotter that visualizes it

.. ipython::

    In [6]: p[0]

    In [7]: type(p[0].psy.plotter)

The visualization and data handling within the psyplot framework is designed to be as easy, flexible and interactive as possible. The appearance of a plot is controlled by the formatoptions of the plotter. In our case, they are the following:

.. ipython::

    In [8]: p.keys()

they can be investigated through the :meth:`Project.keys`, :meth:`~Project.summaries` and :meth:`~Project.docs`, or the corresponding low level methods of the :class:`~psyplot.plotter.Plotter` class, :meth:`~psyplot.plotter.Plotter.show_keys`, :meth:`~psyplot.plotter.Plotter.show_summaries` and :meth:`~psyplot.plotter.Plotter.show_docs`.

Updating a formatoption is straight forward. Each formatoption accepts a certain type of data. Let's say, we want to have a different projection. Then we can look at the types this formatoption accepts using the :meth:`Project.docs`

.. ipython::

    In [9]: p.docs('projection')

Let's use an orthogonal projection. The update goes via the :meth:`Project.update` method which goes all the way down to the :meth:`psyplot.plotter.Plotter.update` and the :meth:`psy_maps.plotters.Projection.update` method of the formatoption.

.. ipython::

    @savefig docs_getting_started_1.png width=4in
    In [10]: p.update(projection='ortho')

Note

Actually, in this case an update of the projection requires that the entire axes is cleared and the plot is drawn again. If you want to know more about it, check the :attr:`~psyplot.plotter.Formatoption.requires_clearing` attribute of the formatoption.

Our framework also let's us update the dimensions of the data we show. For example, if we want to display the field for february, we can type

.. ipython::

    # currently we are displaying january
    In [11]: p[0].time.values

    In [12]: p.update(time='1979-02', method='nearest')

    # now its february
    In [13]: p[0].time.values

which is in our case equivalent for choosing the second index in our time coordinate via

.. ipython::
    :verbatim:

    In [14]: p.update(time=1)

So far for the first quick introduction. If you are interested you are welcomed to visit our :ref:`example galleries <plugins>` or continue with this guide.

In the end, don't forget to close the project in order to delete the data from the memory and close the figures

.. ipython::

    In [15]: p.close(True, True, True)

Choosing the dimension

As you saw already above, the scalar variable 't2m' has multiple time steps and we can control what is shown via the :meth:`~Project.update` method. By default, the :meth:`~psyplot.project.ProjectPlotter.mapplot` plot method chooses the first time step and the first vertical level (if those dimensions exist).

However, you can also specify the exact data slice for your visualization based upon the dimensions in you dataset. When doing that, you basically do not have to care about the exact dimension names in the netCDF files, because those are decoded following the CF Conventions. Hence each of the above dimensions are assigned to one of the general dimensions 't' (time), 'z' (vertical dimension), 'y' (horizontal North-South dimension) and 'x' (horizontal East-West dimension). In our demo file, the dimensions are therefore decoded as 'time' |rarr| 't', 'lev' |rarr| 'z', 'lon' |rarr| 'x', 'lat' |rarr| 'y'.

Hence it is equivalent if you type

.. ipython::

    In [7]: psy.plot.mapplot('demo.nc', name='t2m', t=1)

    @suppress
    In [8]: psy.close('all')

or

.. ipython::

    In [8]: psy.plot.mapplot('demo.nc', name='t2m', time=1)

    @suppress
    In [8]: psy.close('all')

Finally you can also be very specific using the dims keyword via

.. ipython::

    In [9]: psy.plot.mapplot('demo.nc', name='t2m', dims={'time': 1})

    @suppress
    In [8]: psy.close('all')

You can also use the method keyword from the plotting function to use the advantages of the :meth:`xarray.DataArray.sel` method. E.g. to plot the data corresponding to March 1979 you can use

.. ipython::

    In [10]: psy.plot.mapplot('demo.nc', name='t2m', t='1979-03',
       ....:                  method='nearest', z=100000)

    @suppress
    In [8]: psy.close('all')

Note

If your netCDF file does (for whatever reason) not follow the CF Conventions, we interprete the last dimension as the x-dimension, the second last dimension (if existent) as the y-dimension, the third last dimension as the z-dimension. The time dimension however has to have the name 'time'. If that still does not fit your netCDF files, you can specify the correct names in the :attr:`~psyplot.config.rcsetup.rcParams`, namely

.. ipython::

    In [11]: psy.rcParams.find_all('decoder.(x|y|z|t)')

Configuring the appearance of the plot

psyplot is build upon the great and extensive features of the matplotlib package. Hence, our framework can in principle be seen as a high-level interface to the matplotlib functionalities. However you can always access the basic matplotlib objects like figures and axes if you need.

In the psyplot framework, the communication to matplotlib is done via formatoptions that control the appearence of a plot. Each plot method (i.e. each attribute of :attr:`psyplot.project.plot`) has several a set of them and they set up the corresponding plotter.

Formatoptions are all designed for an interactive usage and can usually be controlled with very simple commands. They range from simple formatoptions like :attr:`choosing the title <psy_simple.plotters.Simple2DPlotter.title>` to :attr:`choosing the latitude-longitude box of the data <psy_maps.plotters.FieldPlotter.lonlatbox>`.

The formatoptions depend on the specific plotting method and can be seen via the methods

.. autosummary::

    ~psyplot.project.PlotterInterface.keys
    ~psyplot.project.PlotterInterface.summaries
    ~psyplot.project.PlotterInterface.docs

For example to look at the formatoptions of the :attr:`~psyplot.project.ProjectPlotter.mapplot` method in an interactive session, type

.. ipython::

    In [24]: psy.plot.mapplot.keys(grouped=True)  # to see the fmt keys

    In [25]: psy.plot.mapplot.summaries(['title', 'cbar'])  # to see the fmt summaries

    In [26]: psy.plot.mapplot.docs('title')  # to see the full fmt docs

But of course you can also use the :class:`online documentation <psyplot.project.ProjectPlotter>` of the method your interested in.

To include a formatoption from the beginning, you can simply pass in the key and the desired value as keyword argument, e.g.

.. ipython::

    In [27]: psy.plot.mapplot('demo.nc', name='t2m', title='my title',
       ....:                  cbar='r')

    @suppress
    In [28]: psy.close('all')

This works generally well as long as there are no dimensions in the desired data with the same name as one of the passed in formatoptions. If you want to be really sure, use the fmt keyword via

.. ipython::

    In [28]: psy.plot.mapplot('demo.nc', name='t2m', fmt={'title': 'my title',
       ....:                                              'cbar': 'r'})

The same methodology works for the interactive usage, i.e. you can use

.. ipython::

    In [29]: p.update(title='my title', cbar='r')

    # or
    In [30]: p.update(fmt={'title': 'my title', 'cbar': 'r'})

    @suppress
    In [28]: psy.close('all')


Controlling the update

Automatic update

By default, a call of the :meth:`~psyplot.data.ArrayList.update` method forces an automatic update and redrawing of all the plots. There are however several ways to modify this behavior:

  1. Changing the behavior of one single project

    1. in the initialization of a project using the auto_update keyword

      .. ipython::
      
        In [9]: p = psy.plot.mapplot('demo.nc', name='t2m', auto_update=False)
      
      
    2. setting the :attr:`~psyplot.data.ArrayList.no_auto_update` attribute

      .. ipython::
      
        In [10]: p.no_auto_update = True
      
      
  2. Changing the default configuration in the 'lists.auto_update' key in the :attr:`~psyplot.config.rcsetup.rcParams`

    .. ipython::
    
        In [11]: psy.rcParams['lists.auto_update'] = False
    
        @suppress
        In [11]: psy.rcParams['lists.auto_update'] = True
    
    
  3. Using the :attr:`~psyplot.data.ArrayList.no_auto_update` attribute as a context manager

    .. ipython::
    
         In [12]: with p.no_auto_update:
            ....:    p.update(title='test')
    
    

If you disabled the automatical update via one of the above methods, you have to start the registered updates manually via

.. ipython::

    In [12]: p.update(auto_update=True)

    # or
    In [13]: p.start_update()

    @suppress
    In [28]: psy.close('all')


Direct control on formatoption update

By default, when updating a formatoption, it is checked for each plot whether the formatoption would change during the update or not. If not, the formatoption is not updated. However, sometimes you may want to do that and for this, you can use the force keyword in the :meth:`~psyplot.data.ArrayList.update` method.

Creating and managing multiple plots

Creating multiple plots

One major advantage of the psyplot framework is the systematic management of multiple plots at the same time. To create multiple plots, simply pass in a list of dimension values and/or names. For example

.. ipython::

    In [12]: psy.plot.mapplot('demo.nc', name='t2m', time=[0, 1])

    @suppress
    In [12]: psy.close('all')

created two plots: one for the first and one for the second time step.

Furthermore

.. ipython::

    In [13]: psy.plot.mapplot('demo.nc', name=['t2m', 'u'], time=[0, 1])

    @suppress
    In [13]: psy.close('all')

created four plots. By default, each plot is made in an own figure but you can also use the ax keyword to setup how the plots will be arranged. The sort keyword allows you to sort the plots.

As an example we plot the variables 't2m' and 'u' for the first and second time step into one figure and sort by time. This will produce

.. ipython::

    @savefig docs_multiple_plots.png width=4in
    In [16]: psy.plot.mapplot(
       ....:     'demo.nc', name=['t2m', 'u'], time=[0, 1], ax=(2, 2), sort=['time'],
       ....:     title='%(long_name)s, %b')

    @suppress
    In [18]: psy.close('all')

Warning

As the xarray package, the slicing is based upon positional indexing with lists (see the xarray documentation on positional indexing). Hence you might think of choosing your data slice via psy.plot.mapplot(..., x=[1, 2, 3, 4, 5], ...). However this would result in 5 different plots! Instead you have to write psy.plot.mapplot(..., x=[[1, 2, 3, 4, 5]], ...). The same is true for plotting methods like the :attr:`~psyplot.project.ProjectPlotter.mapvector` method. Since this method needs two variables (one for the latitudinal and one for the longitudinal direction), typing

.. ipython::
    :verbatim:

    In [18]:  psy.plot.mapvector('demo.nc', name=['u', 'v'])
    ValueError: Can only plot 3-dimensional data!

results in a :class:`ValueError`. Instead you have to write

.. ipython::

    In [19]: psy.plot.mapvector('demo.nc', name=[['u', 'v']])

    @suppress
    In [20]: psy.close('all')

Please have a look into the documentations of the :attr:`~psyplot.project.ProjectPlotter.mapvector` and :attr:`~psyplot.project.ProjectPlotter.mapcombined` for getting examples on how to use this methods.

Slicing and filtering the project

Managing a whole lot of plots is basically the same as managing a single plot. However, you can always get the single array and handle it separately.

You can either get it through the usual list slicing (the :class:`Project` class actually is a simple :class:`list` subclass) or you can use meta attributes, dimensions and the specific :attr:`~psyplot.data.InteractiveBase.arr_name` attribute. For the latter one, just call the project with your filtering attributes

This behavior is especially useful if you want to address only some arrays with your update. For example, let's consider we want to choose a 'winter' colormap for the zonal wind variable and a colormap ranging from blue to red for the temperature. Then we could do this via

.. ipython::

    In [24]: p(name='t2m').update(cmap='RdBu_r')

    In [26]: p(name='u').update(cmap='winter')

Note

When doing so, we recommend to temporarily disable the automatic update because then the figure will only be drawn once and the update will be done in parallel.

Hence, it is better to use the context manager :attr:`~psyplot.data.ArrayList.no_auto_update` (see :ref:`intro_auto_updates`)

.. ipython::

    In [27]: with p.no_auto_update:
       ....:     p(name='t2m').update(cmap='RdBu_r')
       ....:     p(name='u').update(cmap='winter')
       ....:     p.start_update()

    @suppress
    In [28]: p.close(True, True)

Finally you can access the plots created by a specific plotting method through the corresponding attribute in the :class:`~psyplot.project.Project` class. In this case this is of course useless because all plots in maps were created by the same plotting method, but it may be helpful when having different plotters in one project (see :ref:`framework`). Anyway, the plots created by the :attr:`~psyplot.project.ProjectPlotter.mapplot` method could be accessed via

.. ipython::

    In [24]: p.mapplot



Saving and loading your project

Within the psyplot framework, you can also save and restore your plots easily and flexibel.

To save your project, use the :meth:`~psyplot.project.Project.save_project` method:

.. ipython::

    @suppress
    In [28]: p = psy.plot.mapplot('demo.nc', name='t2m', time=[0, 1], ax=(1, 2))

    In [28]: p.save_project('my_project.pkl')

This saves the plot-settings into the file 'my_project.pkl', a simple pickle file that you could open by yourself using

.. ipython::

    In [29]: import pickle

    In [30]: with open('my_project.pkl', 'rb') as f:
       ....:     d = pickle.load(f)

    @ suppress
    In [31]: import os
       ....: os.remove('my_project.pkl')

In order to not avoid large project files, we do not store the data but only the filenames of the datasets. Hence, if you want to load the project again, make sure that the datasets are accessible through the path as they are listed in the :attr:`~psyplot.project.Project.dsnames` attribute.

Otherwise you have several options to avoid wrong paths:

  1. Use the alternative_paths parameter and provide for each filename a specific path when you save the project

    .. ipython::
    
        In [29]: p.dsnames
    
        @verbatim
        In [30]: p.save_project(
           ....:     'test.pkl', alternative_paths={'demo.nc': 'other_path.nc'})
    
    
  2. pack the whole data to the place where you want to store the project file

    .. ipython::
    
        @verbatim
        In [31]: p.save_project('target-folder/test.pkl', pack=True)
    
    
  3. specify where the datasets can be found when you load the project:

    .. ipython::
    
        @verbatim
        In [32]: p = psy.Project.load_project(
           ....:     'test.pkl', alternative_paths={'demo.nc': 'other_path.nc'})
    
    
  4. Save the data in the pickle file, too

    .. ipython::
    
        @verbatim
        In [33]: p.save_project('test.pkl', ds_description={'arr'})
    
        @suppress
        In [33]: psy.close('all')
    
    

To restore your project, simply use the :meth:`~psyplot.project.Project.load_project` method via

.. ipython::

    @verbatim
    In [33]: maps = psy.Project.load_project('test.pkl')

Note

Saving a project stores the figure informations like axes positions, background colors, etc. However only the axes informations from from the axes within the project are stored. Other axes in the matplotlib figures are not considered and will not be restored. You can, however, use the alternative_axes keyword in the :meth:`Project.load_project` method if you want to restore your settings and/or customize your plot with the :attr:`~psyplot.plotter.Plotter.post` formatoption (see :ref:`own-scripts`)

Using presets

You can save and load presets to reuse the formatoption settings. For instance, let's say temperature should always use a 'Reds' cmap, the colorbar label should show the long name and the title should be 'time'. This is of course possible via

.. ipython::

    @savefig docs_presets_1.png width=4in
    In [34]: sp = psy.plot.mapplot(
       ....:     'demo.nc', name='t2m', cmap="Reds", clabel="%(long_name)s",
       ....:     title='%(time)s')

But instead of writing this all the time, you can also save it as a preset

.. ipython::

    In [35]: sp.save_preset("t2m-preset")

    @suppress
    In [35]: psy.close('all')

and reload this preset either via the preset keyword

.. ipython::

    @savefig docs_presets_2.png width=4in
    In [36]: sp = psy.plot.mapplot('demo.nc', name='t2m', preset='t2m-preset')

or the :meth:`~psyplot.project.Project.load_preset` method

.. ipython::

    In [37]: sp.load_preset('t2m-preset')

You can list the available presets from the command line

.. ipython::

    In [38]: !psyplot --list-presets

    @suppress
    In [37]: !rm {sp._resolve_preset_path('t2m-preset')}
       ....: psy.close('all')

Adding your own script: The :attr:`~psyplot.plotter.Plotter.post` formatoption

Very likely, you will face the problem that not all your needs are satisfied by the formatoptions in one plotter. You then have two choices:

  1. define your own plotter with new formatoptions (see :ref:`plugins_guide`)

    Pros
    • more structured approach
    • you can enhance the plotter with other formatoptions afterwards and reuse it
    Cons
    • more complicated
    • you always have to ship the module where you define your plotter when you want to :ref:`save and load <save_and_load>` your project
    • can get messy if you define a lot of different plotters
  2. use the :attr:`~psyplot.plotter.Plotter.post` formatoption

    Pros
    Cons
    • may get complicated for large scripts
    • has to be enabled manually by the user

For most of the cases, the :attr:`~psyplot.plotter.Plotter.post` formatoptions is probably what you are looking for (the first option is described in our :ref:`developers guide <developers-guide>`).

This formatoption is designed for applying your own postprocessing script to your plot. It accepts a string that is executed using the built-in :func:`exec` function and is executed at the very end of the plotting. In this python script, the formatoption itself (and therefore the :attr:`~psyplot.plotter.Formatoption.plotter` and :attr:`axes <psyplot.plotter.Formatoption.ax>` can be accessed inside the script through the self variable. An example how to handle this formatoption can be found in :ref:`our example gallery <psyplot_examples:/general/example_post.ipynb>`.