Skip to content

Commit 5512d2f

Browse files
committed
Refactor the data_kind and the virtualfile_to_data functions
1 parent b7b11c5 commit 5512d2f

File tree

3 files changed

+51
-134
lines changed

3 files changed

+51
-134
lines changed

pygmt/clib/session.py

+9-18
Original file line numberDiff line numberDiff line change
@@ -1474,11 +1474,8 @@ def virtualfile_from_data(
14741474
self,
14751475
check_kind=None,
14761476
data=None,
1477-
x=None,
1478-
y=None,
1479-
z=None,
1480-
extra_arrays=None,
1481-
required_z=False,
1477+
vectors=None,
1478+
ncols=1,
14821479
required_data=True,
14831480
):
14841481
"""
@@ -1497,13 +1494,11 @@ def virtualfile_from_data(
14971494
Any raster or vector data format. This could be a file name or
14981495
path, a raster grid, a vector matrix/arrays, or other supported
14991496
data input.
1500-
x/y/z : 1-D arrays or None
1501-
x, y, and z columns as numpy arrays.
1502-
extra_arrays : list of 1-D arrays
1503-
Optional. A list of numpy arrays in addition to x, y, and z.
1504-
All of these arrays must be of the same size as the x/y/z arrays.
1505-
required_z : bool
1506-
State whether the 'z' column is required.
1497+
vectors : list of 1-D arrays or None
1498+
A list of 1-D arrays. Each array will be a column in the table.
1499+
All of these arrays must be of the same size.
1500+
ncols : int
1501+
The minimum number of columns required for the data.
15071502
required_data : bool
15081503
Set to True when 'data' is required, or False when dealing with
15091504
optional virtual files. [Default is True].
@@ -1538,7 +1533,7 @@ def virtualfile_from_data(
15381533
<vector memory>: N = 3 <7/9> <4/6> <1/3>
15391534
"""
15401535
kind = data_kind(
1541-
data, x, y, z, required_z=required_z, required_data=required_data
1536+
data, vectors=vectors, ncols=ncols, required_data=required_data
15421537
)
15431538

15441539
if check_kind:
@@ -1579,11 +1574,7 @@ def virtualfile_from_data(
15791574
warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2)
15801575
_data = (data,) if not isinstance(data, pathlib.PurePath) else (str(data),)
15811576
elif kind == "vectors":
1582-
_data = [np.atleast_1d(x), np.atleast_1d(y)]
1583-
if z is not None:
1584-
_data.append(np.atleast_1d(z))
1585-
if extra_arrays:
1586-
_data.extend(extra_arrays)
1577+
_data = [np.atleast_1d(v) for v in vectors]
15871578
elif kind == "matrix": # turn 2-D arrays into list of vectors
15881579
try:
15891580
# pandas.Series will be handled below like a 1-D numpy.ndarray

pygmt/helpers/utils.py

+41-115
Original file line numberDiff line numberDiff line change
@@ -15,99 +15,7 @@
1515
from pygmt.exceptions import GMTInvalidInput
1616

1717

18-
def _validate_data_input(
19-
data=None, x=None, y=None, z=None, required_z=False, required_data=True, kind=None
20-
):
21-
"""
22-
Check if the combination of data/x/y/z is valid.
23-
24-
Examples
25-
--------
26-
>>> _validate_data_input(data="infile")
27-
>>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6])
28-
>>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6], z=[7, 8, 9])
29-
>>> _validate_data_input(data=None, required_data=False)
30-
>>> _validate_data_input()
31-
Traceback (most recent call last):
32-
...
33-
pygmt.exceptions.GMTInvalidInput: No input data provided.
34-
>>> _validate_data_input(x=[1, 2, 3])
35-
Traceback (most recent call last):
36-
...
37-
pygmt.exceptions.GMTInvalidInput: Must provide both x and y.
38-
>>> _validate_data_input(y=[4, 5, 6])
39-
Traceback (most recent call last):
40-
...
41-
pygmt.exceptions.GMTInvalidInput: Must provide both x and y.
42-
>>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6], required_z=True)
43-
Traceback (most recent call last):
44-
...
45-
pygmt.exceptions.GMTInvalidInput: Must provide x, y, and z.
46-
>>> import numpy as np
47-
>>> import pandas as pd
48-
>>> import xarray as xr
49-
>>> data = np.arange(8).reshape((4, 2))
50-
>>> _validate_data_input(data=data, required_z=True, kind="matrix")
51-
Traceback (most recent call last):
52-
...
53-
pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns.
54-
>>> _validate_data_input(
55-
... data=pd.DataFrame(data, columns=["x", "y"]),
56-
... required_z=True,
57-
... kind="matrix",
58-
... )
59-
Traceback (most recent call last):
60-
...
61-
pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns.
62-
>>> _validate_data_input(
63-
... data=xr.Dataset(pd.DataFrame(data, columns=["x", "y"])),
64-
... required_z=True,
65-
... kind="matrix",
66-
... )
67-
Traceback (most recent call last):
68-
...
69-
pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns.
70-
>>> _validate_data_input(data="infile", x=[1, 2, 3])
71-
Traceback (most recent call last):
72-
...
73-
pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z.
74-
>>> _validate_data_input(data="infile", y=[4, 5, 6])
75-
Traceback (most recent call last):
76-
...
77-
pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z.
78-
>>> _validate_data_input(data="infile", z=[7, 8, 9])
79-
Traceback (most recent call last):
80-
...
81-
pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z.
82-
83-
Raises
84-
------
85-
GMTInvalidInput
86-
If the data input is not valid.
87-
"""
88-
if data is None: # data is None
89-
if x is None and y is None: # both x and y are None
90-
if required_data: # data is not optional
91-
raise GMTInvalidInput("No input data provided.")
92-
elif x is None or y is None: # either x or y is None
93-
raise GMTInvalidInput("Must provide both x and y.")
94-
if required_z and z is None: # both x and y are not None, now check z
95-
raise GMTInvalidInput("Must provide x, y, and z.")
96-
else: # data is not None
97-
if x is not None or y is not None or z is not None:
98-
raise GMTInvalidInput("Too much data. Use either data or x/y/z.")
99-
# For 'matrix' kind, check if data has the required z column
100-
if kind == "matrix" and required_z:
101-
if hasattr(data, "shape"): # np.ndarray or pd.DataFrame
102-
if len(data.shape) == 1 and data.shape[0] < 3:
103-
raise GMTInvalidInput("data must provide x, y, and z columns.")
104-
if len(data.shape) > 1 and data.shape[1] < 3:
105-
raise GMTInvalidInput("data must provide x, y, and z columns.")
106-
if hasattr(data, "data_vars") and len(data.data_vars) < 3: # xr.Dataset
107-
raise GMTInvalidInput("data must provide x, y, and z columns.")
108-
109-
110-
def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data=True):
18+
def data_kind(data=None, vectors=None, ncols=1, required_data=True):
11119
"""
11220
Check what kind of data is provided to a module.
11321
@@ -129,12 +37,10 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data
12937
Pass in either a file name or :class:`pathlib.Path` to an ASCII data
13038
table, an :class:`xarray.DataArray`, a 1-D/2-D
13139
{table-classes} or an option argument.
132-
x/y : 1-D arrays or None
133-
x and y columns as numpy arrays.
134-
z : 1-D array or None
135-
z column as numpy array. To be used optionally when x and y are given.
136-
required_z : bool
137-
State whether the 'z' column is required.
40+
vectors : list of 1-D arrays or None
41+
1-D arrays.
42+
ncols : int
43+
The minimum number of columns required for the data.
13844
required_data : bool
13945
Set to True when 'data' is required, or False when dealing with
14046
optional virtual files. [Default is True].
@@ -151,25 +57,38 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data
15157
>>> import numpy as np
15258
>>> import xarray as xr
15359
>>> import pathlib
154-
>>> data_kind(data=None, x=np.array([1, 2, 3]), y=np.array([4, 5, 6]))
60+
>>> data_kind(
61+
... data=None, vectors=[np.array([1, 2, 3]), np.array([4, 5, 6])]
62+
... )
15563
'vectors'
156-
>>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None)
64+
>>> data_kind(data=np.arange(10).reshape((5, 2)), vectors=[None, None])
15765
'matrix'
158-
>>> data_kind(data="my-data-file.txt", x=None, y=None)
66+
>>> data_kind(data="my-data-file.txt", vectors=[None, None])
15967
'file'
160-
>>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None)
68+
>>> data_kind(data=pathlib.Path("my-data-file.txt"), vectors=[None, None])
16169
'file'
162-
>>> data_kind(data=None, x=None, y=None, required_data=False)
70+
>>> data_kind(data=None, vectors=[None, None], required_data=False)
16371
'arg'
164-
>>> data_kind(data=2.0, x=None, y=None, required_data=False)
72+
>>> data_kind(data=2.0, vectors=[None, None], required_data=False)
16573
'arg'
166-
>>> data_kind(data=True, x=None, y=None, required_data=False)
74+
>>> data_kind(data=True, vectors=[None, None], required_data=False)
16775
'arg'
16876
>>> data_kind(data=xr.DataArray(np.random.rand(4, 3)))
16977
'grid'
17078
>>> data_kind(data=xr.DataArray(np.random.rand(3, 4, 5)))
17179
'image'
17280
"""
81+
# Check the combination of data and vectors
82+
if data is None and (vectors is None or all(v is None for v in vectors[:ncols])):
83+
if required_data:
84+
raise GMTInvalidInput("No input data provided.")
85+
if (
86+
data is not None
87+
and vectors is not None
88+
and any(v is not None for v in vectors[:ncols])
89+
):
90+
raise GMTInvalidInput("Too much data. Pass in either 'data' or 1-D arrays.")
91+
17392
# determine the data kind
17493
if isinstance(data, (str, pathlib.PurePath)):
17594
kind = "file"
@@ -185,15 +104,22 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data
185104
kind = "matrix"
186105
else:
187106
kind = "vectors"
188-
_validate_data_input(
189-
data=data,
190-
x=x,
191-
y=y,
192-
z=z,
193-
required_z=required_z,
194-
required_data=required_data,
195-
kind=kind,
196-
)
107+
108+
# Check if the data/vector input is valid
109+
if kind == "vectors":
110+
if len(vectors) < ncols:
111+
raise GMTInvalidInput(f"Must provide at least {ncols} 1-D arrays.")
112+
if any(v is None for v in vectors[:ncols]):
113+
raise GMTInvalidInput("Must provide both x and y.")
114+
elif kind == "matrix":
115+
if hasattr(data, "shape"): # np.ndarray or pd.DataFrame
116+
if len(data.shape) == 1 and data.shape[0] < ncols:
117+
raise GMTInvalidInput(f"data must have at least {ncols} columns.")
118+
if len(data.shape) > 1 and data.shape[1] < ncols:
119+
raise GMTInvalidInput(f"data must have at least {ncols} columns.")
120+
if hasattr(data, "data_vars") and len(data.data_vars) < ncols: # xr.Dataset
121+
raise GMTInvalidInput(f"data must have at least {ncols} columns.")
122+
197123
return kind
198124

199125

pygmt/src/contour.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def contour(self, data=None, x=None, y=None, z=None, **kwargs):
116116

117117
with Session() as lib:
118118
file_context = lib.virtualfile_from_data(
119-
check_kind="vector", data=data, x=x, y=y, z=z, required_z=True
119+
check_kind="vector", data=data, vectors=[x, y, z], ncols=3
120120
)
121121
with file_context as fname:
122122
lib.call_module(

0 commit comments

Comments
 (0)