Obsah
1. Xarray: sémantické rozšíření n-rozměrných polí z knihovny NumPy
2. Instalace balíčku xarray i jeho tranzitivních závislostí
3. Instalace při použití správce balíčků PDM
6. Pojmenování os (dimenzí), specifikace souřadnic v jednotlivých dimenzích, pojmenování pole
7. Přiřazení dalších uživatelských atributů
8. Vícedimenzionální koordináty, 3D pole
10. Uložení polí do formátu NetCDF
11. Přečtení koordinát na zvolené ose ve formě nového pole
12. Použití číselných indexů při přístupu k prvkům n-dimenzionálních polí
13. Využití operací isel a sel při přístupu k prvkům n-dimenzionálních polí
14. Základní aritmetické a relační operace s polem (broadcasting)
15. Konverze pole operací where
16. Množina polí: datový typ DataSet
17. Dataset s dvojicí trojrozměrných polí
18. Operace nad poli s odlišnou souřadnou mřížkou
19. Repositář s demonstračními příklady
1. Xarray: sémantické rozšíření n-rozměrných polí z knihovny NumPy
V dnešním článku o programovacím jazyku Python se seznámíme s potenciálně velmi užitečnou knihovnou nazvanou Xarray. Tato knihovna rozšiřuje běžná n-rozměrná pole (ND-array) z knihovny NumPy, protože k těmto polím přidává další sémantické informace. Zejména jsou pole rozšířena o jména souřadných os (tzv. dimenze), o souřadnice (koordináty) na jednotlivých osách, ale taktéž o metainformace přidané uživatelem atd. Tyto informace mohou být uloženy společně s prvky polí do souborů ve standardním (a standardizovaném) formátu NetCDF, což umožňuje jak import dat s metainformacemi, tak i jejich sdílení mezi různými systémy. Navíc je možné s dimenzemi a koordináty dále pracovat, což si ukážeme na několika demonstračních příkladech (nejedná se tedy o pouhá statická data).
Obrázek 1: Logo knihovny xarray.
Jen pro zajímavost se podívejme, jak by bylo možné reprezentovat pozici figurek na šachovnici s tím, že obě osy jsou pojmenovány (files a ranks jsou oficiální jména používaná namísto sloupců a řádků) a navíc jsou použity „sémantické“ souřadnice, tedy označení sloupců písmeny a řádků čísly 1–8 (namísto použití celočíselných indexů 0 až 7). K poli reprezentujícím šachovnici jsou navíc přidány i další metainformace: jméno a uživatské atributy:
import numpy as np import xarray as xr chessboard = np.array([" "]*64).reshape(8, 8) chessboard[0, 0] = "♚" chessboard[1, 5] = "♔" chessboard[2, 5] = "♙" chessboard[3, 4] = "♜" files = ["a", "b", "c", "d", "e", "f", "g", "h"] ranks = np.linspace(1, 8, 8, dtype=np.int8) array = xr.DataArray(chessboard, name="Saavedra position", dims=("files", "ranks"), coords={"files":files, "ranks":ranks}) array.attrs["units"] = "chess pieces" array.attrs["description"] ="White to move and win", array.attrs["metadata"] = {"played by": "Fernando Saavedra", "winner": "white", "see also": "https://www.youtube.com/watch?v=Mg2OOsQPURs",} print(array) print() # výběr sloupce print(array.sel(files="c")) print() # výběr řádku print(array.sel(ranks=6)) print()
2. Instalace balíčku xarray i jeho tranzitivních závislostí
Balíček xarray je dostupný na PyPi, takže je jeho instalace snadná. V případě, že již máte nainstalovány základní závislosti, což jsou v tomto konkrétním případě knihovny Numpy a Pandas, proběhne instalace prakticky okamžitě:
$ pip3 install --user xarray Collecting xarray Downloading xarray-0.16.2-py3-none-any.whl (736 kB) |████████████████████████████████| 736 kB 1.0 MB/s Collecting setuptools>=38.4 Downloading setuptools-59.6.0-py3-none-any.whl (952 kB) |████████████████████████████████| 952 kB 2.1 MB/s Requirement already satisfied: pandas>=0.25 in ./.local/lib/python3.6/site-packages (from xarray) (1.1.4) Requirement already satisfied: numpy>=1.15 in ./.local/lib/python3.6/site-packages (from xarray) (1.19.4) Requirement already satisfied: python-dateutil>=2.7.3 in ./.local/lib/python3.6/site-packages (from pandas>=0.25->xarray) (2.8.1) Requirement already satisfied: pytz>=2017.2 in /usr/lib/python3.6/site-packages (from pandas>=0.25->xarray) (2017.2) Requirement already satisfied: six>=1.5 in ./.local/lib/python3.6/site-packages (from python-dateutil>=2.7.3->pandas>=0.25->xarray) (1.11.0) Installing collected packages: setuptools, xarray Successfully installed setuptools-59.6.0 xarray-0.16.2
Na počítači s „čistou“ instalací Pythonu a nástroje pip se navíc musí nainstalovat i všechny potřebné závislosti, což jsou především již výše zmíněné knihovny Numpy a Pandas. Instalace tedy potrvá déle, protože je nutné stáhnout několik megabajtů s tranzitivně závislými (to je tedy slovní spojení!) balíčky:
$ pip3 install --user xarray Collecting xarray Downloading xarray-2023.1.0-py3-none-any.whl (973 kB) |████████████████████████████████| 973 kB 1.5 MB/s Collecting numpy>=1.20 Downloading numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB) |████████████████████████████████| 17.3 MB 3.1 MB/s Collecting packaging>=21.3 Downloading packaging-23.2-py3-none-any.whl (53 kB) |████████████████████████████████| 53 kB 963 kB/s Collecting pandas>=1.3 Downloading pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.4 MB) |████████████████████████████████| 12.4 MB 4.3 MB/s Collecting python-dateutil>=2.8.2 Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB) |████████████████████████████████| 247 kB 3.1 MB/s Collecting tzdata>=2022.1 Downloading tzdata-2023.3-py2.py3-none-any.whl (341 kB) |████████████████████████████████| 341 kB 3.1 MB/s Collecting pytz>=2020.1 Downloading pytz-2023.3.post1-py2.py3-none-any.whl (502 kB) |████████████████████████████████| 502 kB 3.2 MB/s Requirement already satisfied: six>=1.5 in /usr/lib/python3/dist-packages (from python-dateutil>=2.8.2->pandas>=1.3->xarray) (1.14.0) Installing collected packages: numpy, packaging, python-dateutil, tzdata, pytz, pandas, xarray Successfully installed numpy-1.24.4 packaging-23.2 pandas-2.0.3 python-dateutil-2.8.2 pytz-2023.3.post1 tzdata-2023.3 xarray-2023.1.0
3. Instalace při použití správce balíčků PDM
Knihovnu xarray je pochopitelně možné nastavit i jako závislý balíček (přímá závislost, popř. vývojová závislost) při použití nástroje PDM, který byl na Rootu popsán předminulý týden. Ukažme si pro úplnost, jak by mohl vypadat projekt, který je na knihovně xarray postaven.
Nejprve si necháme vytvořit kostru nového projektu (prozatím bez závislostí):
$ pdm init
Poctivě odpovíme na položené otázky (většinou jen potvrzením klávesou Enter, další odpovědi jsou označeny tučně):
Creating a pyproject.toml for PDM... Please enter the Python interpreter to use 0. /usr/bin/python (3.11) 1. /usr/bin/python3.11 (3.11) 2. /usr/bin/python3 (3.11) Please select (0): 0 Would you like to create a virtualenv with /usr/bin/python? [y/n] (y): y Virtualenv is created successfully at /home/ptisnovs/test2/.venv Is the project a library that is installable? If yes, we will need to ask a few more questions to include the project name and build backend [y/n] (n): n License(SPDX name) (MIT): Author name (): Pavel Tisnovsky Author email (): tisnik@nowhere.us Python requires('*' to allow any) (>=3.8): Project is initialized successfully
Do právě vzniklého projektu ve druhém kroku přidáme závislost na balíčku xarray:
$ pdm add xarray
V tomto kroku by se měly nainstalovat stejné balíčky, jako při instalaci přes pip (viz předchozí kapitolu):
Adding packages to default dependencies: xarray 🔒 Lock successful Changes are written to pyproject.toml. Synchronizing working set with resolved packages: 7 to add, 0 to update, 0 to remove ✔ Install six 1.16.0 successful ✔ Install packaging 23.2 successful ✔ Install python-dateutil 2.8.2 successful ✔ Install xarray 2023.1.0 successful ✔ Install tzdata 2023.3 successful ✔ Install pytz 2023.3.post1 successful ✔ Install pandas 2.0.3 successful 🎉 All complete!
A výsledný projektový soubor by měl vypadat následovně:
[project] name = "" version = "" description = "" authors = [ {name = "", email = ""}, ] dependencies = [ "numpy>=1.24.4", "xarray>=2023.1.0", ] requires-python = ">=3.8" readme = "README.md" license = {text = "MIT"}
4. Kontrola instalace
Kontrola, zda instalace proběhla v pořádku, bude prozatím velmi jednoduchá. Naimportujeme knihovnu xarray a necháme si k ní zobrazit nápovědu, což je triviální:
import xarray as xr help(xr)
Po spuštění tohoto skriptu, popř. při zadání obou příkazů do interaktivního prostředí Pythonu by se měla zobrazit nápověda k balíčku (a pochopitelně i import by měl proběhnout bez vyhození výjimky):
Help on package xarray: NAME xarray PACKAGE CONTENTS backends (package) coding (package) conventions convert core (package) indexes (package) plot (package) static (package) testing tests (package) tutorial util (package) CLASSES ... ... ...
5. Datový typ DataArray
Základním datovým typem, nad nímž je knihovna xarray postavena, je typ nazvaný DataArray. Jedná se o obecné n-rozměrné homogenní pole, které ovšem může mít pojmenované jednotlivé osy (dimenze), může mít specifikovány souřadnice v n-rozměrné mřížce (například se může jednat o časová razítka atd.) a dokonce můžeme poli přiřadit i další atributy. Důležité je, že tyto atributy jsou zachovány i při provádění různých operací s tímto datovým typem:
Help on class DataArray in module xarray.core.dataarray: class DataArray(xarray.core.common.AbstractArray, xarray.core.common.DataWithCoords, xarray.core.arithmetic.DataArrayArithmetic, xarray.core._aggregations.DataArrayAggregations) | DataArray(data: 'Any' = <NA>, coords: 'Sequence[Sequence[Any] | pd.Index | DataArray] | Mapping[Any, Any] | None' = None, dims: 'Hashable | Sequence[Hashable] | None' = None, name: 'Hashable | None' = None, attrs: 'Mapping | None' = None, indexes: 'dict[Hashable, Index] | None' = None, fastpath: 'bool' = False) -> 'None' | | N-dimensional array with labeled coordinates and dimensions. | | DataArray provides a wrapper around numpy ndarrays that uses | labeled dimensions and coordinates to support metadata aware | operations. The API is similar to that for the pandas Series or | DataFrame, but DataArray objects can have any number of dimensions, | and their contents have fixed data types. | | Additional features over raw numpy arrays: | | - Apply operations over dimensions by name: ``x.sum('time')``. | - Select or assign values by integer location (like numpy): | ``x[:10]`` or by label (like pandas): ``x.loc['2014-01-01']`` or | ``x.sel(time='2014-01-01')``. | - Mathematical operations (e.g., ``x - y``) vectorize across | multiple dimensions (known in numpy as "broadcasting") based on | dimension names, regardless of their original order. | - Keep track of arbitrary metadata in the form of a Python | dictionary: ``x.attrs`` | - Convert to a pandas Series: ``x.to_series()``. | | Getting items from or doing mathematical operations with a | DataArray always returns another DataArray. ... ... ...
Podívejme se nyní, jak lze takové pole vytvořit. Do konstruktoru DataArray můžeme v tom nejjednodušším případě předat n-rozměrné pole z Numpy, například jednotkovou matici vytvořenou konstruktorem numpy.identity(řád matice):
import numpy as np import xarray as xr array = xr.DataArray(np.identity(10)) print(array)
Funkce print zobrazí obsah objektu DataArray v čitelné podobě:
<xarray.DataArray (dim_0: 10, dim_1: 10)> array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]) Dimensions without coordinates: dim_0, dim_1
6. Pojmenování os (dimenzí), specifikace souřadnic v jednotlivých dimenzích, pojmenování pole
Osy (zde nazývané dimenze) mají výchozí jméno „dim0“, „dim1“ atd. (podle počtu dimenzí pole). Tyto nicneříkající názvy můžeme přejmenovat při vytváření objektu typu DataArray specifikací nepovinného parametru dims. Je to snadné – konstruktoru postačí předat n-tici či seznam s názvy:
import numpy as np import xarray as xr array = xr.DataArray(np.identity(10), dims=("x", "y")) print(array)
Názvy os (dimenzí) se po spuštění tohoto skriptu zobrazí ve výsledku:
<xarray.DataArray (x: 10, y: 10)> array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]) Dimensions without coordinates: x, y
Stejně důležité je v praxi specifikace souřadnic v jednotlivých souřadných osách. Prvky pole tedy budou mít kromě celočíselných indexů přiřazeny i konkrétní souřadnice (například polohu, teplotu, čas, atd.). Podívejme se, jak lze specifikovat souřadnice v ose, kterou jsme si explicitně pojmenovali „x“. Souřadnice se předávají ve slovníku coords:
import numpy as np import xarray as xr array = xr.DataArray(np.identity(10), dims=("x", "y"), coords={"x":[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]}) print(array)
Výsledné pole (povšimněte si sekce Coordinates i sekce navazující):
<xarray.DataArray (x: 10, y: 10)> array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]) Coordinates: * x (x) int64 10 20 30 40 50 60 70 80 90 100 Dimensions without coordinates: y
Samozřejmě nám nic nebrání ve specifikaci souřadnic pro obě osy našeho dvourozměrného pole. Tentokrát budou souřadnice specifikovány vektorem:
import numpy as np import xarray as xr xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(np.identity(10), dims=("x", "y"), coords={"x":xcoords, "y":ycoords}) print(array)
Výsledek:
<xarray.DataArray (x: 10, y: 10)> array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0
A konečně ještě poli přiřadíme nějaké jméno, což je další standardní atribut:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}) print(array)
Nyní může výsledek vypadat následovně:
<xarray.DataArray 'Temperature measurement' (x: 10, y: 10)> array([[ 2.32647600e+01, 2.80514346e+00, 1.55639979e+01, -8.66640844e+00, 1.02604000e+01, 1.43061846e+01, -1.50550198e+00, 1.15990642e+01, 2.33254049e+01, 2.33481511e+01], [ 1.67234930e+01, 2.51284240e+01, 5.54117143e+00, -5.58609990e+00, 2.67385215e+01, -9.45194606e+00, 2.69381374e+01, -2.64076192e+00, 1.76559359e+00, 2.17843960e+01], ... ... ... [ 1.18639075e+01, 2.75731272e+01, 2.09509091e+01, 1.56939796e+01, 2.43195638e+01, 1.36336352e+01, 1.56647959e+01, 2.36473805e+01, 2.77993553e+01, 1.35592233e+01], [-1.64663391e+00, -1.18151743e+00, 1.86746830e+01, -1.54580596e+00, 6.31361942e+00, -8.33521270e+00, 9.62469380e+00, 1.19293630e+00, -8.01198193e+00, 3.77753591e+00]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0
7. Přiřazení dalších uživatelských atributů
K polím, resp. přesněji řečeno k objektům typu DataArray, lze snadno přiřadit i další atributy, které mohou být libovolného typu (jen je nutné si dát pozor na případné problémy při ukládání polí do formátu netCDF, což si ukážeme dále). V dalším demonstračním příkladu do vytvořeného pole přidáme několik dalších pojmenovaných atributů zápisem do slovníku DataArray.attrs. Povšimněte si, že hodnotami mohou být například i další slovníky, n-tice atd. atd.:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}) array.attrs["units"] = "centigrees" array.attrs["description"] ="Local temperature values measured in grid", array.attrs["measured by"] = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} print(array)
Atributy jsou vypsány společně se všemi dalšími informacemi o poli:
<xarray.DataArray 'Temperature measurement' (x: 10, y: 10)> array([[10.5832851 , -2.32490932, -5.5881393 , 0.418035 , 10.60766768, 24.48854489, 14.06688637, -8.7774459 , 5.05229998, -1.11231137], [17.7745931 , -6.78789311, 1.63416214, 10.47049785, -8.0736508 , 7.89837523, 19.39429883, -6.35163393, 18.23288826, -8.52934274], [17.01104059, -2.77683528, 6.76134728, 5.36721515, 9.62797089, 11.25519146, 17.77977519, -2.85977703, -9.77967287, 5.53854267], [16.39795885, 15.3228364 , 24.77271425, 23.84860142, 12.59105118, 26.71223278, 12.93472326, 12.87686291, 2.38745519, -9.89202765], [ 0.43474102, 3.90017104, 11.66830096, 17.85339982, -8.94414913, -0.95883465, -7.04423858, -0.99590894, -5.34842274, 0.05459278], [22.93909906, 6.71161717, 19.96209108, -8.56968358, -6.4280693 , -4.74889956, 7.48179102, 14.95668487, 26.34024394, 20.73641704], [20.35649459, 4.91876629, -1.81175122, -7.98758547, -8.3296374 , 13.6681342 , -1.54770907, 19.54327137, 2.00960747, 19.51355718], [ 7.26390215, 5.07340915, -8.7700297 , 2.30132815, 28.69651615, 4.44835462, 1.50046012, 26.87604918, 18.78270925, -5.10117866], [14.30112364, 0.50389193, 6.99194032, -8.30147546, 21.33919 , -6.77261547, -0.92636757, 4.08006382, 15.24589196, 12.08606029], [ 4.580669 , 10.00008772, 15.56234634, -6.53134212, 11.8121812 , -6.37603482, 10.71300997, 25.90596186, 17.79681462, 9.47069227]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 Attributes: units: centigrees description: ('Local temperature values measured in grid',) measured by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
Alternativně je možné atributy nastavit přímo při konstrukci pole typu DataArray, a to specifikací nepovinného atributu attrs. Tento postup je ukázán na dalším příkladu:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array)
Výsledek bude vypadat takto:
<xarray.DataArray 'Temperature measurement' (x: 10, y: 10)> array([[23.2576271 , -2.15779202, 17.5443984 , 21.07597796, 10.56049287, 29.07426336, -3.65466472, 9.79700303, 11.76340949, 14.79519823], [24.63298382, 17.74715879, 0.60019018, -0.8598743 , 5.07014205, 11.37652831, 2.25657343, 21.52399783, -2.36153402, 11.16127697], [17.38473719, 4.88292401, 16.746297 , 2.73729571, 20.98158579, 19.05244522, -7.10327481, 19.02122539, 5.72791239, 23.42325136], [29.63847975, 22.97463667, -8.71680645, 23.17846176, -1.50461374, -2.5930478 , 14.94336694, 26.55283565, 1.77010509, 26.68586834], [ 6.26370305, 15.29240368, 24.82652106, 28.41207708, 25.02094536, 1.58548248, -9.07739674, 24.42454772, 27.31781816, 18.87826056], [ 8.61993635, 19.14889656, 19.76866295, 17.83522801, -3.56479117, 18.59088179, 29.7079522 , -7.79032716, 22.02287043, 28.06321914], [-5.07439459, -2.55489334, -6.80073133, -8.11352206, 14.07990422, 4.10234674, 14.97487831, 21.35397355, 2.77982491, 6.14347479], [ 0.44349296, 29.28705309, 3.74470177, 3.58964699, 9.63450628, -3.01921671, 6.1531892 , 16.83808275, -1.46114794, 22.98582524], [-0.25700688, 16.88119187, -8.86471902, 21.76825227, -8.78371084, 20.8501691 , 27.77968551, -7.85864626, -9.07993012, -8.70373661], [-4.28096512, 22.95255284, 8.17621111, 19.76970543, 11.0936614 , -4.10484249, 26.17673632, -0.80783274, -1.92738157, -4.07289025]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
8. Vícedimenzionální koordináty, 3D pole
Knihovna xarray umožňuje používat i takzvané vícedimenzionální koordináty. Je to ukázáno na dalším příkladu, v němž se používají logické koordináty longitues a latitudes, které jsou odvozeny z fyzických koordinát reprezentovaných hodnotami na osách x a y. Takto zapsané koordináty se používají například při vykreslování, což je téma navazujícího článku:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(2, 2) longitudes = [[-99.83, -99.32], [-99.79, -99.23]] latitudes = [[42.25, 42.21], [42.63, 42.59]] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords=dict( lon=(["x", "y"], longitudes), lat=(["x", "y"], latitudes), ), attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array)
Z výpisu je patrné, že pole má stále velikost pouze 2×2 prvky:
array([[19.06037142, -7.87284455], [27.94244297, 10.81820761]]) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 Dimensions without coordinates: x, y Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
Nezávisle na volbě koordinát samozřejmě můžeme pracovat i s vícerozměrnými poli. Příkladem může být pole se třemi osami (dimenzemi), kde třetí dimenzí bude čas:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(2, 2, 3) longitudes = [[-99.83, -99.32], [-99.79, -99.23]] latitudes = [[42.25, 42.21], [42.63, 42.59]] times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords=dict( lon=(["x", "y"], longitudes), lat=(["x", "y"], latitudes), time=times, ), attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array)
Výsldek:
array([[[ 1.09668425, 2.00419139, 5.51502503], [20.53512088, -4.33673027, 18.02246637]], [[25.73788498, -2.226619 , 19.64667429], [ 0.55226708, 3.82939432, 11.70746199]]]) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Dimensions without coordinates: x, y Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
9. Přístup k atributům polí
Velmi snadno je možné přistupovat k následujícím atributům objektu DataArray: jeho jménu, uloženým hodnotám, koordinátům, dimenzím a taktéž k uživatelským atributům. Je to velmi snadné – použije se standardní tečková notace tak, jak je to ukázáno v následujícím demonstračním příkladu:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(2, 2, 3) longitudes = [[-99.83, -99.32], [-99.79, -99.23]] latitudes = [[42.25, 42.21], [42.63, 42.59]] times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords=dict( lon=(["x", "y"], longitudes), lat=(["x", "y"], latitudes), time=times, ), attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print() print("Name") print(array.name) print() print("Values") print(array.values) print() print("Coords") print(array.coords) print() print("Attributes") print(array.attrs) print()
Zobrazené výsledky ukazují, jaké informace jsou v jednotlivých atributech uloženy (nadpisy jsem zvýraznil ručně):
<xarray.DataArray 'Temperature measurement' (x: 2, y: 2, time: 3)> array([[[-2.25069401, -1.24318547, -8.79751399], [ 8.69104338, 29.770204 , -0.62751908]], [[ 7.60403569, 16.76061332, 10.71663607], [ 2.55393168, 20.98851206, 4.2413965 ]]]) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Dimensions without coordinates: x, y Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... Name Temperature measurement Values [[[-2.25069401 -1.24318547 -8.79751399] [ 8.69104338 29.770204 -0.62751908]] [[ 7.60403569 16.76061332 10.71663607] [ 2.55393168 20.98851206 4.2413965 ]]] Coords Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes {'units': 'centigrees', 'description': 'Local temperature values measured in grid', 'measured_by': {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, 0, 0)}}
10. Uložení polí do formátu NetCDF
Knihovna xarray podporuje ukládání dat (tedy jak n-rozměrných polí s metainformacemi, tak i sady těchto polí) do formátu NetCDF neboli Network Common Data Form. Jedná se o poměrně starý formát, protože práce na na něm začaly již v roce 1988. Tento původní formát se dnes nazývá „classic NetCDF format“ a zajímavé je, že se stále používá (ostatně stále se používá například i grafický formát GIF z roku 1987, resp. jeho nová verze z roku 1989). Čtvrtá verze formátu NetCDF z roku 2008 již přímo zmiňuje formát HDF. NetCDF obsahuje hlavičku za níž následují jednotlivá pole a prakticky libovolné množství záznamů s metadaty o těchto polích ve formě dvojic klíč-hodnota. V současnosti je tento formát podporován v mnoha ekosystémech, například v ekosystému programovacího jazyka Python, jazyka Julia, v Mathematice, MATLABu atd. (již z tohoto výčtu je patrné, že tento formát je primárně určen pro uložení vědeckých dat).
Uložení objektu typu DataArray do formátu NetCDF je na první pohled snadné, protože postačuje použít metodu DataArray.to_netcdf, které se předá jméno výsledného souboru:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) array.to_netcdf("temperatures.nc")
Ve skutečnosti je však pro uložení v tomto formátu nezbytné nainstalovat buď balíček netCDF4-Python nebo knihovnu SciPy, která potřebnou funkcionalitu podporuje:
$ pdm add scipy Adding packages to default dependencies: scipy 🔒 Lock successful Changes are written to pyproject.toml. Synchronizing working set with resolved packages: 1 to add, 0 to update, 0 to remove ✔ Install scipy 1.9.3 successful 🎉 All complete!
Výsledkem bude upravený projektový soubor a samozřejmě i všechny nainstalované balíčky:
[project] name = "" version = "" description = "" authors = [ {name = "", email = ""}, ] dependencies = [ "numpy>=1.24.4", "xarray>=2023.1.0", "scipy>=1.9.3", ] requires-python = ">=3.8" readme = "README.md" license = {text = "MIT"}
Nyní se pokusíme o export. Ten se ovšem nepodaří, neboť atributy ve formátu netCDF4 mohou být jen číselné či řetězcové. Proto si skript nepatrně upravíme do podoby:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid")) print(array) array.to_netcdf("temperatures.nc")
Nyní již k exportu došlo a vznikl tento soubor:
$ ls -l temperatures.nc -rw-r--r--. 1 ptisnovs ptisnovs 1356 Oct 28 08:55 temperatures.nc $ file temperatures.nc temperatures.nc: NetCDF Data Format data (64-bit offset)
Ten je přenositelný i do všech dalších systémů, které formát netCDF podporují.
11. Přečtení koordinát na zvolené ose ve formě nového pole
Samotné koordináty jsou v knihovně xarray velmi důležitým konceptem. Můžeme je kdykoli přečíst selektorem pole.coords[název_dimenze], přičemž výsledkem bude (jak typické) další pole typu DataArray, jehož jméno bude odpovídat jménu zvolené (čtené) osy (tedy dimenze). Ostatně si to můžeme velmi snadno otestovat:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10, 3) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords={"x":xcoords, "y":ycoords, "time":times}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(array.coords["x"]) print() print(array.coords["y"]) print() print(array.coords["time"]) print()
Výsledky jsou poměrně dobře čitelné:
<xarray.DataArray 'x' (x: 10)> array([ 10., 20., 30., 40., 50., 60., 70., 80., 90., 100.]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 <xarray.DataArray 'y' (y: 10)> array([-100., -90., -80., -70., -60., -50., -40., -30., -20., -10.]) Coordinates: * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 <xarray.DataArray 'time' (time: 3)> array(['2023-10-01', '2023-10-02', '2023-10-03'], dtype='<U10') Coordinates: * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03'
Dokonce je možné použít ještě kratší zápis pole[název_dimenze] (což ovšem může být matoucí – nejedná se o výběr prvku pole):
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10, 3) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords={"x":xcoords, "y":ycoords, "time":times}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(array["x"]) print() print(array["y"]) print() print(array["time"]) print()
Výsledky by měly být totožné s předchozím příkladem:
<xarray.DataArray 'x' (x: 10)> array([ 10., 20., 30., 40., 50., 60., 70., 80., 90., 100.]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 <xarray.DataArray 'y' (y: 10)> array([-100., -90., -80., -70., -60., -50., -40., -30., -20., -10.]) Coordinates: * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 <xarray.DataArray 'time' (time: 3)> array(['2023-10-01', '2023-10-02', '2023-10-03'], dtype='<U10') Coordinates: * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03'
12. Použití číselných indexů při přístupu k prvkům n-dimenzionálních polí
Podobně jako v knihovně Numpy, i v případě použití xarray lze k prvkům n-dimenzionálních polí přistupovat přes celočíselné indexy, čímž se vlastně obchází koncept dimenzí a koordinát. Podívejme se na příklad trojrozměrného pole o rozměrech 10×10×3 prvky. Použijeme tři typy celočíselných indexů:
import numpy as np import xarray as xr temperatures = np.arange(0, 300).reshape((10, 10, 3)) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords={"x":xcoords, "y":ycoords, "time":times}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(array[0]) print() print(array[0][2]) print() print(array[:,2]) print()
Výsledná pole (protože výsledkem jsou buď skaláry nebo pole):
array[0] - získání 2D matice z 3D pole <xarray.DataArray 'Temperature measurement' (y: 10, time: 3)> array([[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23], [24, 25, 26], [27, 28, 29]]) Coordinates: x float64 10.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... array[0][2] - získání 1D vektoru z 3D pole (sloupec časů) <xarray.DataArray 'Temperature measurement' (time: 3)> array([6, 7, 8]) Coordinates: x float64 10.0 y float64 -80.0 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... array[:,2] - získání 2D matice z 3D pole (jiný typ řezu) <xarray.DataArray 'Temperature measurement' (x: 10, time: 3)> array([[ 6, 7, 8], [ 36, 37, 38], [ 66, 67, 68], [ 96, 97, 98], [126, 127, 128], [156, 157, 158], [186, 187, 188], [216, 217, 218], [246, 247, 248], [276, 277, 278]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 y float64 -80.0 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
13. Využití operací isel a sel při přístupu k prvkům n-dimenzionálních polí
K prvkům n-dimenzionálních polí (což jsou buď přímo skalární prvky, vektory, matice nebo další pole) lze ovšem přistupovat i dalšími způsoby. Knihovna Xarray v tomto ohledu nabízí především operaci nazvanou isel, která umožňuje specifikovat osu (její název) a současně i index (nikoli souřadnici!) na této ose. To je mnohdy mnohem čitelnější, než použití pozičních argumentů. Ostatně si to můžeme velmi snadno otestovat:
print(array.isel(x=4)) print() print(array.isel(y=4)) print()
Ještě užitečnější je specifikace osy a souřadnice na této ose (tedy nikoli indexu). Pro tento účel je určena operace nazvaná sel:
print(array.sel(time="2023-10-02")) print()
Výsledkem výběru je opět datová struktura DataArray, což nám umožňuje navázání většího množství výběrových operací:
print(array.sel(time="2023-10-02").isel(x=0)) print() print(array.sel(time="2023-10-02").isel(x=0).isel(y=0)) print()
Vše si vyzkoušíme na následujícím příkladu s trojrozměrným polem:
import numpy as np import xarray as xr temperatures = np.arange(0, 300).reshape((10, 10, 3)) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) times = ["2023-10-01", "2023-10-02", "2023-10-03"] array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y", "time"), coords={"x":xcoords, "y":ycoords, "time":times}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(array.isel(x=4)) print() print(array.isel(y=4)) print() print(array.sel(time="2023-10-02")) print() print(array.sel(time="2023-10-02").isel(x=0)) print() print(array.sel(time="2023-10-02").isel(x=0).isel(y=0)) print()
Výsledky všech pěti výběrů z původního pole:
print(array.isel(x=4)) <xarray.DataArray 'Temperature measurement' (y: 10, time: 3)> array([[120, 121, 122], [123, 124, 125], [126, 127, 128], [129, 130, 131], [132, 133, 134], [135, 136, 137], [138, 139, 140], [141, 142, 143], [144, 145, 146], [147, 148, 149]]) Coordinates: x float64 50.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(array.isel(y=4)) <xarray.DataArray 'Temperature measurement' (x: 10, time: 3)> array([[ 12, 13, 14], [ 42, 43, 44], [ 72, 73, 74], [102, 103, 104], [132, 133, 134], [162, 163, 164], [192, 193, 194], [222, 223, 224], [252, 253, 254], [282, 283, 284]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 y float64 -60.0 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(array.sel(time="2023-10-02")) <xarray.DataArray 'Temperature measurement' (x: 10, y: 10)> array([[ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28], [ 31, 34, 37, 40, 43, 46, 49, 52, 55, 58], [ 61, 64, 67, 70, 73, 76, 79, 82, 85, 88], [ 91, 94, 97, 100, 103, 106, 109, 112, 115, 118], [121, 124, 127, 130, 133, 136, 139, 142, 145, 148], [151, 154, 157, 160, 163, 166, 169, 172, 175, 178], [181, 184, 187, 190, 193, 196, 199, 202, 205, 208], [211, 214, 217, 220, 223, 226, 229, 232, 235, 238], [241, 244, 247, 250, 253, 256, 259, 262, 265, 268], [271, 274, 277, 280, 283, 286, 289, 292, 295, 298]]) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 time <U10 '2023-10-02' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(array.sel(time="2023-10-02").isel(x=0)) <xarray.DataArray 'Temperature measurement' (y: 10)> array([ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28]) Coordinates: x float64 10.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -40.0 -30.0 -20.0 -10.0 time <U10 '2023-10-02' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(array.sel(time="2023-10-02").isel(x=0).isel(y=0)) <xarray.DataArray 'Temperature measurement' ()> array(1) Coordinates: x float64 10.0 y float64 -100.0 time <U10 '2023-10-02' Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
14. Základní aritmetické a relační operace s polem (broadcasting)
V knihovně NumPy je implementována podpora pro provádění operací nad celými poli (součet, rozdíl) a taktéž podpora pro takzvaný broadcasting, tedy pro rozšíření pole s menší dimenzí tak, aby bylo možné zvolenou operaci provést (tedy limitně lze přičíst skalár ke každému prvku libovolného N-rozměrného pole). Tato podpora je přidána i do knihovny Xarray, což si opět můžeme velmi snadno otestovat a zjistit, do jaké míry budou výsledky odpovídat očekávání. Provedeme postupně změnu znaménka všech prvků pole, vynásobení všech prvků pole skalárem a nakonec zaokrouhlení všech prvků vstupního pole:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(5, 5) xcoords = np.linspace(10, 100, 5) ycoords = np.linspace(-100, -10, 5) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(-array) print() print(array * 2) print() print(array.round()) print()
Vypočtené výsledky:
print(array) <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[24.0898683 , 5.73595219, 28.81478359, 7.86339653, 18.6217371 ], [17.57468852, -0.39366504, -0.42952136, 1.30559859, -0.12386281], [-2.20172524, 21.12189613, 13.50420003, 14.56997888, 29.93619462], [-4.07788389, -2.99061956, 19.7810614 , 15.83607309, 14.84566202], [ 4.07018656, 24.21400408, -1.86752453, -2.01035754, 26.85957395]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(-array) <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[-24.0898683 , -5.73595219, -28.81478359, -7.86339653, -18.6217371 ], [-17.57468852, 0.39366504, 0.42952136, -1.30559859, 0.12386281], [ 2.20172524, -21.12189613, -13.50420003, -14.56997888, -29.93619462], [ 4.07788389, 2.99061956, -19.7810614 , -15.83607309, -14.84566202], [ -4.07018656, -24.21400408, 1.86752453, 2.01035754, -26.85957395]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
print(array * 2) <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[48.1797366 , 11.47190438, 57.62956718, 15.72679306, 37.2434742 ], [35.14937704, -0.78733008, -0.85904272, 2.61119719, -0.24772562], [-4.40345048, 42.24379226, 27.00840006, 29.13995776, 59.87238924], [-8.15576777, -5.98123913, 39.56212279, 31.67214619, 29.69132405], [ 8.14037312, 48.42800817, -3.73504907, -4.02071507, 53.71914789]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0
print(array.round()) <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[24., 6., 29., 8., 19.], [18., -0., -0., 1., -0.], [-2., 21., 14., 15., 30.], [-4., -3., 20., 16., 15.], [ 4., 24., -2., -2., 27.]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ...
Podporovány jsou i relační operace. V následujícím demonstračním příkladu je proveden broadcasting operace porovnání hodnoty každého prvku s nulou:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(5, 5) xcoords = np.linspace(10, 100, 5) ycoords = np.linspace(-100, -10, 5) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(array >= 0)
původní pole: <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[22.01427636, 20.07058816, -5.52517483, 27.1273763 , 26.60849883], [ 9.64803697, -6.15887599, 18.84069071, -7.4845254 , 17.78351831], [24.62328894, 0.85433323, -8.92058503, -2.88662501, 6.82371433], [17.61267925, 25.79661576, 9.87869585, -2.3779088 , -2.54645438], [15.31556007, 2.24089859, -2.55861374, 4.18911297, -5.9337331 ]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... nové pole obsahující pouze pravdivostní hodnoty: <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[ True, True, False, True, True], [ True, False, True, False, True], [ True, True, False, False, True], [ True, True, True, False, False], [ True, True, False, True, False]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0
15. Konverze pole operací where
Operace where umožňuje konvertovat jedno pole na pole jiné na základě zapsané podmínky. Snadno například můžeme zkonvertovat 2D pole s naměřenými teplotami na jiné pole, v němž budou zapsány pouze informace o tom, zda je teplota pod bodem nebo nad bodem mrazu:
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(5, 5) xcoords = np.linspace(10, 100, 5) ycoords = np.linspace(-100, -10, 5) array = xr.DataArray(temperatures, name="Temperature measurement", dims=("x", "y"), coords={"x":xcoords, "y":ycoords}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array) print() print(xr.where(array <= 0, "freezing", "above 0°C"))
Po spuštění tohoto skriptu se nejdříve vypíše původní pole a posléze pole obsahující pouze prvky se dvěma možnými hodnotami (mrzne, teplota nad bodem mrazu):
<xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([[ 5.09135141, 14.87737191, 26.92786592, 19.53857612, 1.99129423], [-5.40843065, 20.34764162, 17.73095444, -4.79341295, 15.15640546], [ 5.01924974, 4.57123972, 24.84870027, 24.26450989, -2.46815504], [-6.95834721, -3.89831812, 22.64776767, 10.0516584 , 20.16149552], [12.52627837, 23.93366452, 25.61122643, -8.34828794, 20.23273152]]) Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0 Attributes: units: centigrees description: Local temperature values measured in grid measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... <xarray.DataArray 'Temperature measurement' (x: 5, y: 5)> array([['above 0°C', 'above 0°C', 'above 0°C', 'above 0°C', 'above 0°C'], ['freezing', 'above 0°C', 'above 0°C', 'freezing', 'above 0°C'], ['above 0°C', 'above 0°C', 'above 0°C', 'above 0°C', 'freezing'], ['freezing', 'freezing', 'above 0°C', 'above 0°C', 'above 0°C'], ['above 0°C', 'above 0°C', 'above 0°C', 'freezing', 'above 0°C']], dtype='<U9') Coordinates: * x (x) float64 10.0 32.5 55.0 77.5 100.0 * y (y) float64 -100.0 -77.5 -55.0 -32.5 -10.0
16. Množina polí: datový typ DataSet
Datový typ DataSet umožňuje uložení většího množství polí do jediné datové struktury s tím, že tato pole mohou sdílet stejné osy (dimenze) a koordináty:
Obrázek 2: Vztah mezi poli, jejich počtem dimenzí a osami (dimenzemi) v DataSetu.
Prozatím si ukažme velmi jednoduchý příklad, v němž bude množina polí obsahovat dvě pole s teplotami a rychlostí větru (obě pole jsou, na rozdíl od obrázku, jen dvoudimenzionální):
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) wind = 100*np.random.rand(10, 10) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) dataset = xr.Dataset({"temperatures": (["x", "y"], temperatures), "wind": (["x", "y"], wind)}, coords={"x":xcoords, "y":ycoords}) print(dataset)
DataSet se zobrazí takto:
Dimensions: (x: 10, y: 10) Coordinates: * x (x) float64 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 * y (y) float64 -100.0 -90.0 -80.0 -70.0 ... -30.0 -20.0 -10.0 Data variables: temperatures (x, y) float64 -6.135 -4.41 5.464 14.3 ... 16.53 -4.616 -2.667 wind (x, y) float64 31.51 4.919 50.45 52.2 ... 23.57 62.56 89.88
Shodné rozměry polí se při konstrukci datasetu kontrolují (protože se sdílí koordináty):
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(10, 10) wind = 100*np.random.rand(20, 20) xcoords = np.linspace(10, 100, 10) ycoords = np.linspace(-100, -10, 10) dataset = xr.Dataset({"temperatures": (["x", "y"], temperatures), "wind": (["x", "y"], wind)}, coords={"x":xcoords, "y":ycoords}) print(dataset)
V tomto případě je vyhozena výjimka informující o rozdílných rozměrech polí:
ValueError: conflicting sizes for dimension 'x': length 20 on 'wind' and length 10 on {'x': 'temperatures', 'y': 'temperatures'}
17. Dataset s dvojicí trojrozměrných polí
V dalším demonstračním příkladu je ukázána konstrukce Datasetu s dvojicí trojrozměrných polí. První pole obsahuje teploty změřené v mřížce v určitém časovém intervalu, druhé pole pak obsahuje sílu větru. Obě pole sdílí osy (dimenze) i souřadnice na nich (koordináty):
import numpy as np import xarray as xr temperatures = -10 + 40*np.random.rand(2, 2, 3) wind = 100*np.random.rand(2, 2, 3) longitudes = [[-99.83, -99.32], [-99.79, -99.23]] latitudes = [[42.25, 42.21], [42.63, 42.59]] times = ["2023-10-01", "2023-10-02", "2023-10-03"] dataset = xr.Dataset({"temperatures": (["x", "y", "time"], temperatures), "wind": (["x", "y", "time"], wind)}, coords={ "lon": (["x", "y"], longitudes), "lat": (["x", "y"], latitudes), "time": times}) print(dataset) print() print("Temperatures:") print(dataset["temperatures"]) print() print("Wind:") print(dataset["wind"])
Po spuštění tohoto skriptu se zobrazí jak celý původní dataset, tak i jednotlivá pole, která z něho byla přečtena:
<xarray.Dataset> Dimensions: (x: 2, y: 2, time: 3) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Dimensions without coordinates: x, y Data variables: temperatures (x, y, time) float64 24.16 -1.54 0.6033 ... 26.13 23.31 -6.954 wind (x, y, time) float64 62.01 67.12 43.23 ... 49.47 20.94 62.97 Temperatures: <xarray.DataArray 'temperatures' (x: 2, y: 2, time: 3)> array([[[24.16258661, -1.53957247, 0.60330884], [18.93054758, 24.08112626, -2.11897159]], [[27.1337761 , 1.59080802, -5.46317688], [26.13170764, 23.30735881, -6.95440354]]]) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Dimensions without coordinates: x, y Wind: <xarray.DataArray 'wind' (x: 2, y: 2, time: 3)> array([[[62.01079346, 67.11864438, 43.22516902], [19.36766203, 48.18517828, 39.55293065]], [[88.02526604, 6.9555846 , 13.95148965], [49.46957983, 20.93642261, 62.97467643]]]) Coordinates: lon (x, y) float64 -99.83 -99.32 -99.79 -99.23 lat (x, y) float64 42.25 42.21 42.63 42.59 * time (time) <U10 '2023-10-01' '2023-10-02' '2023-10-03' Dimensions without coordinates: x, y
18. Operace nad poli s odlišnou souřadnou mřížkou
Jednou z nejužitečnějších vlastností knihovny Xarray je zajištění, že pokud bude nějaká operace (například součet) probíhat nad dvěma poli s odlišnou souřadnou mřížkou, bude operace provedena takovým způsobem, aby se sčítaly prvky se shodnými souřadnicemi. Výsledné pole bude mít obecně menší rozměry, než obě pole vstupní, protože ty prvky polí, které nemají odpovídající protějšek ve druhém poli, nebudou přidány do výsledku.
Tuto vlastnost si můžeme ukázat na součtu dvou dvourozměrných polí (tedy vlastně matic). První pole obsahuje prvky v mřížce 1–10×1–10 (představte si tedy šachovnici se sloupci 1, 2, … 10 a řádky 1, 2, …10), druhé pole prvky v mřížce 6–15×6–15. Výsledkem bude pole s prvky ležícími v mřížce 6–10×6–10, což je vlastně jen jedna čtvrtina velikosti původních polí (25 prvků vs. 100 prvků):
import numpy as np import xarray as xr temperatures1 = np.arange(100).reshape(10, 10) temperatures2 = np.array([100]*100).reshape(10, 10) xcoords1 = np.linspace(1, 10, 10) ycoords1 = np.linspace(1, 10, 10) print("Coordinates for 1st DataArray") print("x:", xcoords1) print("y:", ycoords1) print() xcoords2 = np.linspace(6, 15, 10) ycoords2 = np.linspace(6, 15, 10) print("Coordinates for 2nd DataArray") print("x:", xcoords2) print("y:", ycoords2) print() array1 = xr.DataArray(temperatures1, name="Temperature measurement #1", dims=("x", "y"), coords={"x":xcoords1, "y":ycoords1}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid #1", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) array2 = xr.DataArray(temperatures2, name="Temperature measurement #2", dims=("x", "y"), coords={"x":xcoords2, "y":ycoords2}, attrs=dict( units = "centigrees", description ="Local temperature values measured in grid #2", measured_by = {"name": "ThermometerBot", "vendor": "BIY", "version": (1, 0, 0)} )) print(array1) print() print() print(array2) print() print() array3 = array1 + array2 print(array3) print() print()
Výsledek získaný po spuštění tohoto demonstračního příkladu:
Coordinates for 1st DataArray x: [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] y: [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] Coordinates for 2nd DataArray x: [ 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.] y: [ 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.] Array #1: <xarray.DataArray 'Temperature measurement #1' (x: 10, y: 10)> array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [70, 71, 72, 73, 74, 75, 76, 77, 78, 79], [80, 81, 82, 83, 84, 85, 86, 87, 88, 89], [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]) Coordinates: * x (x) float64 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 * y (y) float64 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 Attributes: units: centigrees description: Local temperature values measured in grid #1 measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... Array #2: <xarray.DataArray 'Temperature measurement #2' (x: 10, y: 10)> array([[100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]]) Coordinates: * x (x) float64 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 * y (y) float64 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 Attributes: units: centigrees description: Local temperature values measured in grid #2 measured_by: {'name': 'ThermometerBot', 'vendor': 'BIY', 'version': (1, ... Array #1 + Array #2: <xarray.DataArray (x: 5, y: 5)> array([[155, 156, 157, 158, 159], [165, 166, 167, 168, 169], [175, 176, 177, 178, 179], [185, 186, 187, 188, 189], [195, 196, 197, 198, 199]]) Coordinates: * x (x) float64 6.0 7.0 8.0 9.0 10.0 * y (y) float64 6.0 7.0 8.0 9.0 10.0
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si ukázali v dnešním článku, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu knihovnu xarray a její závislosti, zejména Numpy):
20. Odkazy na Internetu
- Xarray documentation
https://docs.xarray.dev/en/stable/index.html - Xarray na stránkách PyPi
https://pypi.org/project/xarray/ - Xarray tutorial
https://tutorial.xarray.dev/intro.html - Repositář knihovny xarray
https://github.com/pydata/xarray - A Simple File Format for NumPy Arrays
https://docs.scipy.org/doc/numpy-1.14.2/neps/npy-format.html - Stránky projektu Numpy
https://numpy.org/ - Stránky projektu Pandas
https://pandas.pydata.org/ - numpy.lib.format
https://numpy.org/devdocs/reference/generated/numpy.lib.format.html - The NumPy array: a structure for efficient numerical computation
https://arxiv.org/pdf/1102.1523.pdf - A Gentle Introduction to Pandas Data Analysis (on Kaggle)
https://www.youtube.com/watch?v=_Eb0utIRdkw&list=PL7RwtdVQXQ8oYpuIIDWR0SaaSCe8ZeZ7t&index=4 - Speed Up Your Pandas Dataframes
https://www.youtube.com/watch?v=u4_c2LDi4b8&list=PL7RwtdVQXQ8oYpuIIDWR0SaaSCe8ZeZ7t&index=5 - numpy.ndarray.tofile
https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tofile.html#numpy.ndarray.tofile - numpy.fromfile
https://numpy.org/doc/stable/reference/generated/numpy.fromfile.html - How to read part of binary file with numpy?
https://stackoverflow.com/questions/14245094/how-to-read-part-of-binary-file-with-numpy - How to read binary files in Python using NumPy?
https://stackoverflow.com/questions/39762019/how-to-read-binary-files-in-python-using-numpy - numpy.save
https://numpy.org/doc/stable/reference/generated/numpy.save.html#numpy.save - numpy.load
https://numpy.org/doc/stable/reference/generated/numpy.load.html#numpy.load - SciPy
https://scipy.org/ - Načítání a ukládání dat uložených v N-rozměrných polích v jazyku Go
https://www.root.cz/clanky/nacitani-a-ukladani-dat-ulozenych-v-n-rozmernych-polich-v-jazyku-go/ - Network Common Data Form (NetCDF)
https://www.unidata.ucar.edu/software/netcdf/ - xray: N D Labeled Arrays and Datasets | SciPy 2015 | Stephan Hoyer
https://www.youtube.com/watch?v=X0pAhJgySxk - Xarray Tutorial | xarray fundamentals (Youtube, přetočte si prvních osm nebo devět minut přípravy)
https://www.youtube.com/watch?v=a339Q5F48UQ