hvplot 0.9.3a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hvplot/__init__.py +322 -0
- hvplot/_version.py +16 -0
- hvplot/backend_transforms.py +329 -0
- hvplot/converter.py +2855 -0
- hvplot/cudf.py +26 -0
- hvplot/dask.py +42 -0
- hvplot/data/crime.csv +56 -0
- hvplot/datasets.yaml +48 -0
- hvplot/fugue.py +64 -0
- hvplot/ibis.py +21 -0
- hvplot/intake.py +32 -0
- hvplot/interactive.py +968 -0
- hvplot/networkx.py +625 -0
- hvplot/pandas.py +30 -0
- hvplot/plotting/__init__.py +63 -0
- hvplot/plotting/andrews_curves.py +99 -0
- hvplot/plotting/core.py +2288 -0
- hvplot/plotting/lag_plot.py +34 -0
- hvplot/plotting/parallel_coordinates.py +85 -0
- hvplot/plotting/scatter_matrix.py +220 -0
- hvplot/polars.py +21 -0
- hvplot/sample_data.py +30 -0
- hvplot/streamz.py +21 -0
- hvplot/tests/__init__.py +0 -0
- hvplot/tests/conftest.py +44 -0
- hvplot/tests/data/README.md +5 -0
- hvplot/tests/data/RGB-red.byte.tif +0 -0
- hvplot/tests/plotting/__init__.py +0 -0
- hvplot/tests/plotting/testcore.py +108 -0
- hvplot/tests/plotting/testohlc.py +34 -0
- hvplot/tests/plotting/testscattermatrix.py +138 -0
- hvplot/tests/test_links.py +99 -0
- hvplot/tests/testbackend_transforms.py +89 -0
- hvplot/tests/testcharts.py +452 -0
- hvplot/tests/testfugue.py +46 -0
- hvplot/tests/testgeo.py +468 -0
- hvplot/tests/testgeowithoutgv.py +60 -0
- hvplot/tests/testgridplots.py +259 -0
- hvplot/tests/testhelp.py +75 -0
- hvplot/tests/testibis.py +17 -0
- hvplot/tests/testinteractive.py +1442 -0
- hvplot/tests/testnetworkx.py +26 -0
- hvplot/tests/testoperations.py +385 -0
- hvplot/tests/testoptions.py +596 -0
- hvplot/tests/testoverrides.py +74 -0
- hvplot/tests/testpanel.py +70 -0
- hvplot/tests/testpatch.py +135 -0
- hvplot/tests/testplotting.py +69 -0
- hvplot/tests/teststreaming.py +28 -0
- hvplot/tests/testtransforms.py +39 -0
- hvplot/tests/testui.py +383 -0
- hvplot/tests/testutil.py +378 -0
- hvplot/tests/util.py +82 -0
- hvplot/ui.py +1032 -0
- hvplot/util.py +677 -0
- hvplot/utilities.py +129 -0
- hvplot/xarray.py +62 -0
- hvplot-0.9.3a1.dist-info/LICENSE +29 -0
- hvplot-0.9.3a1.dist-info/METADATA +243 -0
- hvplot-0.9.3a1.dist-info/RECORD +63 -0
- hvplot-0.9.3a1.dist-info/WHEEL +5 -0
- hvplot-0.9.3a1.dist-info/entry_points.txt +2 -0
- hvplot-0.9.3a1.dist-info/top_level.txt +1 -0
hvplot/util.py
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides utilities to convert data and projections
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from collections.abc import Hashable
|
|
8
|
+
|
|
9
|
+
from functools import wraps
|
|
10
|
+
from packaging.version import Version
|
|
11
|
+
from types import FunctionType
|
|
12
|
+
|
|
13
|
+
import bokeh
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
import param
|
|
17
|
+
import holoviews as hv
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import panel as pn
|
|
21
|
+
|
|
22
|
+
panel_available = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
panel_available = False
|
|
25
|
+
|
|
26
|
+
hv_version = Version(hv.__version__)
|
|
27
|
+
bokeh_version = Version(bokeh.__version__)
|
|
28
|
+
bokeh3 = bokeh_version >= Version('3.0')
|
|
29
|
+
param2 = Version(param.__version__) >= Version('2.0rc4')
|
|
30
|
+
_fugue_ipython = None # To be set to True in tests to mock ipython
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def with_hv_extension(func, extension='bokeh', logo=False):
|
|
34
|
+
"""If hv.extension is not loaded, load before calling function"""
|
|
35
|
+
|
|
36
|
+
@wraps(func)
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
if extension and not getattr(hv.extension, '_loaded', False):
|
|
39
|
+
from . import hvplot_extension
|
|
40
|
+
|
|
41
|
+
hvplot_extension(extension, logo=logo)
|
|
42
|
+
return func(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_ipy():
|
|
48
|
+
try:
|
|
49
|
+
ip = get_ipython() # noqa
|
|
50
|
+
except NameError:
|
|
51
|
+
ip = None
|
|
52
|
+
return ip
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_crs(crs):
|
|
56
|
+
"""
|
|
57
|
+
Checks if the crs represents a valid grid, projection or ESPG string.
|
|
58
|
+
|
|
59
|
+
(Code copied and adapted from https://github.com/fmaussion/salem)
|
|
60
|
+
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
63
|
+
>>> p = check_crs('epsg:26915 +units=m')
|
|
64
|
+
>>> p.srs
|
|
65
|
+
'+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs'
|
|
66
|
+
>>> p = check_crs('wrong')
|
|
67
|
+
>>> p is None
|
|
68
|
+
True
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
A valid crs if possible, otherwise None.
|
|
73
|
+
"""
|
|
74
|
+
import pyproj
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
crs_type = pyproj.crs.CRS
|
|
78
|
+
except AttributeError:
|
|
79
|
+
|
|
80
|
+
class Dummy:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
crs_type = Dummy
|
|
84
|
+
|
|
85
|
+
if isinstance(crs, pyproj.Proj):
|
|
86
|
+
out = crs
|
|
87
|
+
elif isinstance(crs, crs_type):
|
|
88
|
+
out = pyproj.Proj(crs.to_wkt(), preserve_units=True)
|
|
89
|
+
elif isinstance(crs, dict) or isinstance(crs, str):
|
|
90
|
+
if isinstance(crs, str):
|
|
91
|
+
try:
|
|
92
|
+
crs = pyproj.CRS.from_wkt(crs)
|
|
93
|
+
except RuntimeError:
|
|
94
|
+
# quick fix for https://github.com/pyproj4/pyproj/issues/345
|
|
95
|
+
crs = crs.replace(' ', '').replace('+', ' +')
|
|
96
|
+
try:
|
|
97
|
+
out = pyproj.Proj(crs, preserve_units=True)
|
|
98
|
+
except RuntimeError:
|
|
99
|
+
try:
|
|
100
|
+
out = pyproj.Proj(init=crs, preserve_units=True)
|
|
101
|
+
except RuntimeError:
|
|
102
|
+
out = None
|
|
103
|
+
else:
|
|
104
|
+
out = None
|
|
105
|
+
return out
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def proj_is_latlong(proj):
|
|
109
|
+
"""Shortcut function because of deprecation."""
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
return proj.is_latlong()
|
|
113
|
+
except AttributeError:
|
|
114
|
+
return proj.crs.is_geographic
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def proj_to_cartopy(proj):
|
|
118
|
+
"""
|
|
119
|
+
Converts a pyproj.Proj to a cartopy.crs.Projection
|
|
120
|
+
|
|
121
|
+
(Code copied from https://github.com/fmaussion/salem)
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
proj: pyproj.Proj
|
|
126
|
+
the projection to convert
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
a cartopy.crs.Projection object
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
import cartopy.crs as ccrs
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
from osgeo import osr
|
|
136
|
+
|
|
137
|
+
has_gdal = True
|
|
138
|
+
except ImportError:
|
|
139
|
+
has_gdal = False
|
|
140
|
+
|
|
141
|
+
input_proj = proj
|
|
142
|
+
proj = check_crs(input_proj)
|
|
143
|
+
if proj is None:
|
|
144
|
+
raise ValueError(f'Invalid proj projection {input_proj!r}')
|
|
145
|
+
|
|
146
|
+
srs = proj.srs
|
|
147
|
+
if has_gdal:
|
|
148
|
+
import warnings
|
|
149
|
+
|
|
150
|
+
with warnings.catch_warnings():
|
|
151
|
+
# Avoiding this warning could be done by setting osr.UseExceptions(),
|
|
152
|
+
# except there might be a risk to break the code of users leveraging
|
|
153
|
+
# GDAL on their side or through other libraries. So we just silence it.
|
|
154
|
+
warnings.filterwarnings(
|
|
155
|
+
'ignore',
|
|
156
|
+
category=FutureWarning,
|
|
157
|
+
message=r'Neither osr\.UseExceptions\(\) nor osr\.DontUseExceptions\(\) has '
|
|
158
|
+
r'been explicitly called\. In GDAL 4\.0, exceptions will be enabled '
|
|
159
|
+
'by default',
|
|
160
|
+
)
|
|
161
|
+
# this is more robust, as srs could be anything (espg, etc.)
|
|
162
|
+
s1 = osr.SpatialReference()
|
|
163
|
+
s1.ImportFromProj4(proj.srs)
|
|
164
|
+
if s1.ExportToProj4():
|
|
165
|
+
srs = s1.ExportToProj4()
|
|
166
|
+
|
|
167
|
+
km_proj = {
|
|
168
|
+
'lon_0': 'central_longitude',
|
|
169
|
+
'lat_0': 'central_latitude',
|
|
170
|
+
'x_0': 'false_easting',
|
|
171
|
+
'y_0': 'false_northing',
|
|
172
|
+
'lat_ts': 'latitude_true_scale',
|
|
173
|
+
'o_lon_p': 'central_rotated_longitude',
|
|
174
|
+
'o_lat_p': 'pole_latitude',
|
|
175
|
+
'k': 'scale_factor',
|
|
176
|
+
'zone': 'zone',
|
|
177
|
+
}
|
|
178
|
+
km_globe = {
|
|
179
|
+
'a': 'semimajor_axis',
|
|
180
|
+
'b': 'semiminor_axis',
|
|
181
|
+
}
|
|
182
|
+
km_std = {
|
|
183
|
+
'lat_1': 'lat_1',
|
|
184
|
+
'lat_2': 'lat_2',
|
|
185
|
+
}
|
|
186
|
+
kw_proj = {}
|
|
187
|
+
kw_globe = {}
|
|
188
|
+
kw_std = {}
|
|
189
|
+
for s in srs.split('+'):
|
|
190
|
+
s = s.split('=')
|
|
191
|
+
if len(s) != 2:
|
|
192
|
+
continue
|
|
193
|
+
k = s[0].strip()
|
|
194
|
+
v = s[1].strip()
|
|
195
|
+
try:
|
|
196
|
+
v = float(v)
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
if k == 'proj':
|
|
200
|
+
if v == 'longlat':
|
|
201
|
+
cl = ccrs.PlateCarree
|
|
202
|
+
elif v == 'tmerc':
|
|
203
|
+
cl = ccrs.TransverseMercator
|
|
204
|
+
kw_proj['approx'] = True
|
|
205
|
+
elif v == 'lcc':
|
|
206
|
+
cl = ccrs.LambertConformal
|
|
207
|
+
elif v == 'merc':
|
|
208
|
+
cl = ccrs.Mercator
|
|
209
|
+
elif v == 'utm':
|
|
210
|
+
cl = ccrs.UTM
|
|
211
|
+
elif v == 'stere':
|
|
212
|
+
cl = ccrs.Stereographic
|
|
213
|
+
elif v == 'ob_tran':
|
|
214
|
+
cl = ccrs.RotatedPole
|
|
215
|
+
else:
|
|
216
|
+
raise NotImplementedError(f'Unknown projection {v}')
|
|
217
|
+
if k in km_proj:
|
|
218
|
+
if k == 'zone':
|
|
219
|
+
v = int(v)
|
|
220
|
+
kw_proj[km_proj[k]] = v
|
|
221
|
+
if k in km_globe:
|
|
222
|
+
kw_globe[km_globe[k]] = v
|
|
223
|
+
if k in km_std:
|
|
224
|
+
kw_std[km_std[k]] = v
|
|
225
|
+
|
|
226
|
+
globe = None
|
|
227
|
+
if kw_globe:
|
|
228
|
+
globe = ccrs.Globe(ellipse='sphere', **kw_globe)
|
|
229
|
+
if kw_std:
|
|
230
|
+
kw_proj['standard_parallels'] = (kw_std['lat_1'], kw_std['lat_2'])
|
|
231
|
+
|
|
232
|
+
# mercatoooor
|
|
233
|
+
if cl.__name__ == 'Mercator':
|
|
234
|
+
kw_proj.pop('false_easting', None)
|
|
235
|
+
kw_proj.pop('false_northing', None)
|
|
236
|
+
if 'scale_factor' in kw_proj:
|
|
237
|
+
kw_proj.pop('latitude_true_scale', None)
|
|
238
|
+
elif cl.__name__ == 'Stereographic':
|
|
239
|
+
kw_proj.pop('scale_factor', None)
|
|
240
|
+
if 'latitude_true_scale' in kw_proj:
|
|
241
|
+
kw_proj['true_scale_latitude'] = kw_proj['latitude_true_scale']
|
|
242
|
+
kw_proj.pop('latitude_true_scale', None)
|
|
243
|
+
elif cl.__name__ == 'RotatedPole':
|
|
244
|
+
if 'central_longitude' in kw_proj:
|
|
245
|
+
kw_proj['pole_longitude'] = kw_proj['central_longitude'] - 180
|
|
246
|
+
kw_proj.pop('central_longitude', None)
|
|
247
|
+
else:
|
|
248
|
+
kw_proj.pop('latitude_true_scale', None)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
return cl(globe=globe, **kw_proj)
|
|
252
|
+
except TypeError:
|
|
253
|
+
del kw_proj['approx']
|
|
254
|
+
|
|
255
|
+
return cl(globe=globe, **kw_proj)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def process_crs(crs):
|
|
259
|
+
"""
|
|
260
|
+
Parses cartopy CRS definitions defined in one of a few formats:
|
|
261
|
+
|
|
262
|
+
1. EPSG codes: Defined as string of the form "EPSG: {code}" or an integer
|
|
263
|
+
2. proj.4 string: Defined as string of the form "{proj.4 string}"
|
|
264
|
+
3. cartopy.crs.CRS instance
|
|
265
|
+
3. pyproj.Proj or pyproj.CRS instance
|
|
266
|
+
4. WKT string: Defined as string of the form "{WKT string}"
|
|
267
|
+
5. None defaults to crs.PlateCaree
|
|
268
|
+
"""
|
|
269
|
+
missing = []
|
|
270
|
+
try:
|
|
271
|
+
import cartopy.crs as ccrs
|
|
272
|
+
except ImportError:
|
|
273
|
+
missing.append('cartopy')
|
|
274
|
+
try:
|
|
275
|
+
import geoviews as gv # noqa
|
|
276
|
+
except ImportError:
|
|
277
|
+
missing.append('geoviews')
|
|
278
|
+
try:
|
|
279
|
+
import pyproj
|
|
280
|
+
except ImportError:
|
|
281
|
+
missing.append('pyproj')
|
|
282
|
+
if missing:
|
|
283
|
+
raise ImportError(f'Geographic projection support requires: {", ".join(missing)}.')
|
|
284
|
+
|
|
285
|
+
if crs is None:
|
|
286
|
+
return ccrs.PlateCarree()
|
|
287
|
+
elif isinstance(crs, ccrs.CRS):
|
|
288
|
+
return crs
|
|
289
|
+
elif isinstance(crs, pyproj.CRS):
|
|
290
|
+
crs = crs.to_wkt()
|
|
291
|
+
|
|
292
|
+
errors = []
|
|
293
|
+
if isinstance(crs, (str, int)): # epsg codes
|
|
294
|
+
try:
|
|
295
|
+
crs = pyproj.CRS.from_epsg(crs).to_wkt()
|
|
296
|
+
except Exception as e:
|
|
297
|
+
errors.append(e)
|
|
298
|
+
if isinstance(crs, (str, pyproj.Proj)): # proj4/wkt strings
|
|
299
|
+
try:
|
|
300
|
+
return proj_to_cartopy(crs)
|
|
301
|
+
except Exception as e:
|
|
302
|
+
errors.append(e)
|
|
303
|
+
|
|
304
|
+
raise ValueError(
|
|
305
|
+
'Projection must be defined as a EPSG code, proj4 string, '
|
|
306
|
+
'WKT string, cartopy CRS, pyproj.Proj, or pyproj.CRS.'
|
|
307
|
+
) from Exception(*errors)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def is_list_like(obj):
|
|
311
|
+
"""
|
|
312
|
+
Adapted from pandas' is_list_like cython function.
|
|
313
|
+
"""
|
|
314
|
+
return (
|
|
315
|
+
# equiv: `isinstance(obj, abc.Iterable)`
|
|
316
|
+
hasattr(obj, '__iter__')
|
|
317
|
+
and not isinstance(obj, type)
|
|
318
|
+
# we do not count strings/unicode/bytes as list-like
|
|
319
|
+
and not isinstance(obj, (str, bytes))
|
|
320
|
+
# exclude zero-dimensional numpy arrays, effectively scalars
|
|
321
|
+
and not (isinstance(obj, np.ndarray) and obj.ndim == 0)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def is_tabular(data):
|
|
326
|
+
if check_library(data, ['dask', 'streamz', 'pandas', 'geopandas', 'cudf']):
|
|
327
|
+
return True
|
|
328
|
+
elif check_library(data, 'intake'):
|
|
329
|
+
from intake.source.base import DataSource
|
|
330
|
+
|
|
331
|
+
if isinstance(data, DataSource):
|
|
332
|
+
return data.container == 'dataframe'
|
|
333
|
+
else:
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def is_series(data):
|
|
338
|
+
if not check_library(data, ['dask', 'streamz', 'pandas', 'cudf']):
|
|
339
|
+
return False
|
|
340
|
+
elif isinstance(data, pd.Series):
|
|
341
|
+
return True
|
|
342
|
+
elif check_library(data, 'streamz'):
|
|
343
|
+
import streamz.dataframe as sdf
|
|
344
|
+
|
|
345
|
+
return isinstance(data, (sdf.Series, sdf.Seriess))
|
|
346
|
+
elif check_library(data, 'dask'):
|
|
347
|
+
import dask.dataframe as dd
|
|
348
|
+
|
|
349
|
+
return isinstance(data, dd.Series)
|
|
350
|
+
elif check_library(data, 'cudf'):
|
|
351
|
+
import cudf
|
|
352
|
+
|
|
353
|
+
return isinstance(data, cudf.Series)
|
|
354
|
+
else:
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def check_library(obj, library):
|
|
359
|
+
if not isinstance(library, list):
|
|
360
|
+
library = [library]
|
|
361
|
+
return any([obj.__module__.split('.')[0].startswith(lib) for lib in library])
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def is_cudf(data):
|
|
365
|
+
if 'cudf' in sys.modules:
|
|
366
|
+
from cudf import DataFrame, Series
|
|
367
|
+
|
|
368
|
+
return isinstance(data, (DataFrame, Series))
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def is_dask(data):
|
|
372
|
+
if not check_library(data, 'dask'):
|
|
373
|
+
return False
|
|
374
|
+
import dask.dataframe as dd
|
|
375
|
+
|
|
376
|
+
return isinstance(data, (dd.DataFrame, dd.Series))
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def is_polars(data):
|
|
380
|
+
if not check_library(data, 'polars'):
|
|
381
|
+
return False
|
|
382
|
+
import polars as pl
|
|
383
|
+
|
|
384
|
+
return isinstance(data, (pl.DataFrame, pl.Series, pl.LazyFrame))
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def is_intake(data):
|
|
388
|
+
if 'intake' not in sys.modules:
|
|
389
|
+
return False
|
|
390
|
+
from intake.source.base import DataSource
|
|
391
|
+
|
|
392
|
+
return isinstance(data, DataSource)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def is_ibis(data):
|
|
396
|
+
if not check_library(data, 'ibis'):
|
|
397
|
+
return False
|
|
398
|
+
import ibis
|
|
399
|
+
|
|
400
|
+
return isinstance(data, ibis.Expr)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def is_streamz(data):
|
|
404
|
+
if not check_library(data, 'streamz'):
|
|
405
|
+
return False
|
|
406
|
+
import streamz.dataframe as sdf
|
|
407
|
+
|
|
408
|
+
return sdf and isinstance(data, (sdf.DataFrame, sdf.Series, sdf.DataFrames, sdf.Seriess))
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def is_xarray(data):
|
|
412
|
+
if not check_library(data, 'xarray'):
|
|
413
|
+
return False
|
|
414
|
+
from xarray import DataArray, Dataset
|
|
415
|
+
|
|
416
|
+
return isinstance(data, (DataArray, Dataset))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def is_xarray_dataarray(data):
|
|
420
|
+
if not check_library(data, 'xarray'):
|
|
421
|
+
return False
|
|
422
|
+
from xarray import DataArray
|
|
423
|
+
|
|
424
|
+
return isinstance(data, DataArray)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def process_intake(data, use_dask):
|
|
428
|
+
if data.container not in ('dataframe', 'xarray'):
|
|
429
|
+
raise NotImplementedError(
|
|
430
|
+
'Plotting interface currently only '
|
|
431
|
+
'supports DataSource objects declaring '
|
|
432
|
+
'a dataframe or xarray container.'
|
|
433
|
+
)
|
|
434
|
+
if use_dask:
|
|
435
|
+
data = data.to_dask()
|
|
436
|
+
else:
|
|
437
|
+
data = data.read()
|
|
438
|
+
return data
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def is_geodataframe(data):
|
|
442
|
+
if 'spatialpandas' in sys.modules:
|
|
443
|
+
import spatialpandas as spd
|
|
444
|
+
|
|
445
|
+
if isinstance(data, spd.GeoDataFrame):
|
|
446
|
+
return True
|
|
447
|
+
return (
|
|
448
|
+
isinstance(data, pd.DataFrame) and hasattr(data, 'geom_type') and hasattr(data, 'geometry')
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def process_xarray(
|
|
453
|
+
data, x, y, by, groupby, use_dask, persist, gridded, label, value_label, other_dims, kind=None
|
|
454
|
+
):
|
|
455
|
+
import xarray as xr
|
|
456
|
+
|
|
457
|
+
if isinstance(data, xr.Dataset):
|
|
458
|
+
dataset = data
|
|
459
|
+
else:
|
|
460
|
+
name = data.name or label or value_label
|
|
461
|
+
dataset = data.to_dataset(name=name)
|
|
462
|
+
|
|
463
|
+
all_vars = list(other_dims) if other_dims else []
|
|
464
|
+
for var in [x, y, by, groupby]:
|
|
465
|
+
if isinstance(var, list):
|
|
466
|
+
all_vars.extend(var)
|
|
467
|
+
elif isinstance(var, str):
|
|
468
|
+
all_vars.append(var)
|
|
469
|
+
|
|
470
|
+
if not gridded:
|
|
471
|
+
not_found = [
|
|
472
|
+
var for var in all_vars if var not in list(dataset.data_vars) + list(dataset.coords)
|
|
473
|
+
]
|
|
474
|
+
_, extra_vars, extra_coords = process_derived_datetime_xarray(dataset, not_found)
|
|
475
|
+
dataset = dataset.assign_coords(**{var: dataset[var] for var in extra_coords})
|
|
476
|
+
dataset = dataset.assign(**{var: dataset[var] for var in extra_vars})
|
|
477
|
+
|
|
478
|
+
data_vars = list(dataset.data_vars)
|
|
479
|
+
ignore = (by or []) + (groupby or [])
|
|
480
|
+
dims = [c for c in dataset.coords if dataset[c].shape != () and c not in ignore][::-1]
|
|
481
|
+
index_dims = [d for d in dims if d in dataset.indexes]
|
|
482
|
+
|
|
483
|
+
if gridded:
|
|
484
|
+
data = dataset
|
|
485
|
+
if len(dims) < 2:
|
|
486
|
+
dims += [dim for dim in list(data.dims)[::-1] if dim not in dims]
|
|
487
|
+
if not (x or y):
|
|
488
|
+
for c in dataset.coords:
|
|
489
|
+
axis = dataset[c].attrs.get('axis', '')
|
|
490
|
+
if axis.lower() == 'x':
|
|
491
|
+
x = c
|
|
492
|
+
elif axis.lower() == 'y':
|
|
493
|
+
y = c
|
|
494
|
+
if not (x or y):
|
|
495
|
+
x, y = index_dims[:2] if len(index_dims) > 1 else dims[:2]
|
|
496
|
+
elif x and not y:
|
|
497
|
+
y = [d for d in dims if d != x][0]
|
|
498
|
+
elif y and not x:
|
|
499
|
+
x = [d for d in dims if d != y][0]
|
|
500
|
+
if len(dims) > 2 and kind not in ('table', 'dataset') and not groupby:
|
|
501
|
+
dims = list(data.coords[x].dims) + list(data.coords[y].dims)
|
|
502
|
+
groupby = [
|
|
503
|
+
d for d in index_dims if d not in (x, y) and d not in dims and d not in other_dims
|
|
504
|
+
]
|
|
505
|
+
else:
|
|
506
|
+
if use_dask:
|
|
507
|
+
data = dataset.to_dask_dataframe()
|
|
508
|
+
data = data.persist() if persist else data
|
|
509
|
+
else:
|
|
510
|
+
data = dataset.to_dataframe()
|
|
511
|
+
if len(data.index.names) > 1:
|
|
512
|
+
data = data.reset_index()
|
|
513
|
+
if len(dims) == 0:
|
|
514
|
+
dims = ['index']
|
|
515
|
+
if x and not y:
|
|
516
|
+
y = dims[0] if x in data_vars else data_vars
|
|
517
|
+
elif y and not x:
|
|
518
|
+
x = data_vars[0] if y in dims else dims[0]
|
|
519
|
+
elif not x and not y:
|
|
520
|
+
x, y = dims[0], data_vars
|
|
521
|
+
|
|
522
|
+
for var in [x, y]:
|
|
523
|
+
if isinstance(var, list):
|
|
524
|
+
all_vars.extend(var)
|
|
525
|
+
elif isinstance(var, str):
|
|
526
|
+
all_vars.append(var)
|
|
527
|
+
|
|
528
|
+
covered_dims = []
|
|
529
|
+
for var in all_vars:
|
|
530
|
+
if var in dataset.coords:
|
|
531
|
+
covered_dims.extend(dataset[var].dims)
|
|
532
|
+
leftover_dims = [dim for dim in index_dims if dim not in covered_dims + all_vars]
|
|
533
|
+
|
|
534
|
+
if groupby is None:
|
|
535
|
+
groupby = [c for c in leftover_dims if c not in (by or [])]
|
|
536
|
+
return data, x, y, by, groupby
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def process_derived_datetime_xarray(data, not_found):
|
|
540
|
+
from pandas.api.types import is_datetime64_any_dtype as isdate
|
|
541
|
+
|
|
542
|
+
extra_vars = []
|
|
543
|
+
extra_coords = []
|
|
544
|
+
for var in not_found:
|
|
545
|
+
if '.' in var:
|
|
546
|
+
derived_from = var.split('.')[0]
|
|
547
|
+
if isdate(data[derived_from]):
|
|
548
|
+
if derived_from in data.coords:
|
|
549
|
+
extra_coords.append(var)
|
|
550
|
+
else:
|
|
551
|
+
extra_vars.append(var)
|
|
552
|
+
not_found = [var for var in not_found if var not in extra_vars + extra_coords]
|
|
553
|
+
return not_found, extra_vars, extra_coords
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def process_derived_datetime_pandas(data, not_found, indexes=None):
|
|
557
|
+
from pandas.api.types import is_datetime64_any_dtype as isdate
|
|
558
|
+
|
|
559
|
+
indexes = indexes or []
|
|
560
|
+
extra_cols = {}
|
|
561
|
+
for var in not_found:
|
|
562
|
+
if '.' in var:
|
|
563
|
+
parts = var.split('.')
|
|
564
|
+
base_col = parts[0]
|
|
565
|
+
dt_str = parts[-1]
|
|
566
|
+
if base_col in data.columns:
|
|
567
|
+
if isdate(data[base_col]):
|
|
568
|
+
extra_cols[var] = getattr(data[base_col].dt, dt_str)
|
|
569
|
+
elif base_col == 'index':
|
|
570
|
+
if isdate(data.index):
|
|
571
|
+
extra_cols[var] = getattr(data.index, dt_str)
|
|
572
|
+
elif base_col in indexes:
|
|
573
|
+
index = data.axes[indexes.index(base_col)]
|
|
574
|
+
if isdate(index):
|
|
575
|
+
extra_cols[var] = getattr(index, dt_str)
|
|
576
|
+
if extra_cols:
|
|
577
|
+
data = data.assign(**extra_cols)
|
|
578
|
+
not_found = [var for var in not_found if var not in extra_cols.keys()]
|
|
579
|
+
|
|
580
|
+
return not_found, data
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def process_dynamic_args(x, y, kind, **kwds):
|
|
584
|
+
dynamic = {}
|
|
585
|
+
arg_deps = []
|
|
586
|
+
arg_names = []
|
|
587
|
+
|
|
588
|
+
for k, v in list(kwds.items()) + [('x', x), ('y', y), ('kind', kind)]:
|
|
589
|
+
if isinstance(v, param.Parameter):
|
|
590
|
+
dynamic[k] = v
|
|
591
|
+
elif panel_available and isinstance(v, pn.widgets.Widget):
|
|
592
|
+
if Version(pn.__version__) < Version('0.6.4'):
|
|
593
|
+
dynamic[k] = v.param.value
|
|
594
|
+
else:
|
|
595
|
+
dynamic[k] = v
|
|
596
|
+
|
|
597
|
+
for k, v in kwds.items():
|
|
598
|
+
if k not in dynamic and isinstance(v, FunctionType) and hasattr(v, '_dinfo'):
|
|
599
|
+
deps = v._dinfo['dependencies']
|
|
600
|
+
arg_deps += list(deps)
|
|
601
|
+
arg_names += list(k) * len(deps)
|
|
602
|
+
|
|
603
|
+
return dynamic, arg_deps, arg_names
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def filter_opts(eltype, options, backend='bokeh'):
|
|
607
|
+
opts = getattr(hv.Store.options(backend), eltype)
|
|
608
|
+
allowed = [k for g in opts.groups.values() for k in list(g.allowed_keywords)]
|
|
609
|
+
opts = {k: v for k, v in options.items() if k in allowed}
|
|
610
|
+
return opts
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _flatten(line):
|
|
614
|
+
"""
|
|
615
|
+
Flatten an arbitrarily nested sequence.
|
|
616
|
+
|
|
617
|
+
Inspired by: pd.core.common.flatten
|
|
618
|
+
|
|
619
|
+
Parameters
|
|
620
|
+
----------
|
|
621
|
+
line : sequence
|
|
622
|
+
The sequence to flatten
|
|
623
|
+
|
|
624
|
+
Notes
|
|
625
|
+
-----
|
|
626
|
+
This only flattens list, tuple, and dict sequences.
|
|
627
|
+
|
|
628
|
+
Returns
|
|
629
|
+
-------
|
|
630
|
+
flattened : generator
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
for element in line:
|
|
634
|
+
if any(isinstance(element, tp) for tp in (list, tuple, dict)):
|
|
635
|
+
yield from _flatten(element)
|
|
636
|
+
else:
|
|
637
|
+
yield element
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _convert_col_names_to_str(data):
|
|
641
|
+
"""
|
|
642
|
+
Convert column names to string.
|
|
643
|
+
"""
|
|
644
|
+
# There's no generic way to rename columns across tabular object types.
|
|
645
|
+
# `columns` could refer to anything else on the object, e.g. a dim
|
|
646
|
+
# on an xarray DataArray. So this may need to be stricter.
|
|
647
|
+
if not hasattr(data, 'columns') or not hasattr(data, 'rename'):
|
|
648
|
+
return data
|
|
649
|
+
renamed = {
|
|
650
|
+
c: str(c) for c in data.columns if not isinstance(c, str) and isinstance(c, Hashable)
|
|
651
|
+
}
|
|
652
|
+
if renamed:
|
|
653
|
+
data = data.rename(columns=renamed)
|
|
654
|
+
return data
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def instantiate_crs_str(crs_str: str, **kwargs):
|
|
658
|
+
"""
|
|
659
|
+
Instantiate a cartopy.crs.Projection from a string.
|
|
660
|
+
"""
|
|
661
|
+
import cartopy.crs as ccrs
|
|
662
|
+
|
|
663
|
+
if crs_str.upper() == 'GOOGLE_MERCATOR':
|
|
664
|
+
return ccrs.GOOGLE_MERCATOR
|
|
665
|
+
return getattr(ccrs, crs_str)(**kwargs)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def import_datashader():
|
|
669
|
+
datashader = None
|
|
670
|
+
try:
|
|
671
|
+
import datashader
|
|
672
|
+
except ModuleNotFoundError:
|
|
673
|
+
raise ModuleNotFoundError(
|
|
674
|
+
'The `datashader` package must be installed in order to use '
|
|
675
|
+
'datashading features. Install it with pip or conda.'
|
|
676
|
+
) from None
|
|
677
|
+
return datashader
|