Skip to content

**Breaking**: data_kind: Now 'matrix' represents a 2-D numpy array and unrecognized data types fall back to 'vectors' - #3351

Merged
seisman merged 21 commits into
mainfrom
refactor/data_kind
Oct 16, 2024
Merged

**Breaking**: data_kind: Now 'matrix' represents a 2-D numpy array and unrecognized data types fall back to 'vectors'#3351
seisman merged 21 commits into
mainfrom
refactor/data_kind

Conversation

@seisman

@seismanseisman commented Jul 23, 2024

Copy link
Copy Markdown
Member

Need to wait for #3481.

Description of proposed changes

As can be seen in the doctests added in #3480, currently, matrix and vectors kinds are defined like below:

  • vectors kind means data=None and required=True. It means that the input is given via a series of vectors (e.g., x/y/z).
  • matrix represents any data types not recognized as arg/file/stringio/grid/image/geojson. The most common data types include pd.DataFrame, pd.Series, xr.Dataset, np.ndarray, and sequence of sequences.

So, matrix here is an inclusive concept. However, in both Python and GMT, matrix usually means a homogenous 2-D array. When passing a "matrix" data to GMT, we usually treat the "matrix" as a series of vectors. The only exception is when the "matrix" data is a strict matrix (a 2-D numpy array) with data.dtype.kind in "iuf".

pygmt/pygmt/clib/session.py

Lines 1742 to 1755 in d7560fa

elifkind=="matrix": # turn 2-D arrays into list of vectors
ifhasattr(data, "items") andnothasattr(data, "to_frame"):
# pandas.DataFrame or xarray.Dataset types.
# pandas.Series will be handled below like a 1-D numpy.ndarray.
_data= [arrayfor_, arrayindata.items()]
elifhasattr(data, "ndim") anddata.ndim==2anddata.dtype.kindin"iuf":
# Just use virtualfile_from_matrix for 2-D numpy.ndarray
# which are signed integer (i), unsigned integer (u) or
# floating point (f) types
_virtualfile_from=self.virtualfile_from_matrix
_data= (data,)
else:
# Python list, tuple, numpy.ndarray, and pandas.Series types
_data=np.atleast_2d(np.asanyarray(data).T)

I think it makes more sense to use a more strict definition for matrix and let all other data types be vectors. I propose to change the definitions of data kinds to:

  • matrix: a 2-D homogenous numpy.ndarray
  • vectors: fallbacks to this kind for any unrecognized data types, including a dictionary, pd.DataFrame, pd.Series, xr.Dataset, nested lists/tuples, or any other unrecognized data types.

The refactoring is mainly done in 808755d.

Of course, data=None, required=True is still recognized as "vectors". I also think we should give this case another special kind like "none", which makes more sense and can also simplify the codes. [This is done in a separate PR at #3482, but I'm OK to merge #3482 into this PR first before merging this PR into main].

Other data kinds are unchanged.

This is a breaking change since some previous "matrix" data are now recognized "vectors" and previously "vectors" is "none". But I guess very few users are using this function, so the breaking change should have minimal effects.

@seisman
seismanforce-pushed the refactor/data_kind branch from ea90891 to c0a5bdbCompareJuly 23, 2024 08:31
Comment threadpygmt/helpers/utils.py Outdated
Comment threadpygmt/helpers/utils.py Outdated
@seisman
seismanforce-pushed the refactor/data_kind branch from c0a5bdb to 78066a4CompareJuly 23, 2024 08:42
@seisman
seisman marked this pull request as ready for review July 23, 2024 08:47
Comment threadpygmt/helpers/utils.py Outdated
@seismanseisman added maintenance Boring but important stuff for the core devs needs review This PR has higher priority and needs review. labels Jul 23, 2024
@seismanseisman added this to the 0.13.0 milestone Jul 23, 2024
@seismanseisman added the run/benchmark Trigger the benchmark workflow in PRs label Jul 23, 2024
@seisman
seismanforce-pushed the refactor/data_kind branch 3 times, most recently from f160dd6 to 1c517e6CompareJuly 23, 2024 15:37
@seismanseisman changed the title Refactor the data_kind function and improve docstrings[BREAKING CHANGES] Refactor the data_kind function to return "none"/"matrix"/"vectors" data kinds in a more reasonable wayJul 23, 2024
@seisman
seismanforce-pushed the refactor/data_kind branch from 1c517e6 to 21f581aCompareJuly 23, 2024 16:41
@seisman
seismanforce-pushed the refactor/data_kind branch 3 times, most recently from 52b711b to 713d0a2CompareJuly 26, 2024 15:32
@seismanseisman mentioned this pull request Aug 1, 2024
39 tasks
@seisman
seisman requested a review from a teamAugust 1, 2024 06:20
Comment threadpygmt/helpers/utils.py Outdated
return "geojson"

# A 2-D numpy.ndarray
if isinstance(data, np.ndarray) and data.ndim == 2:

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably can change this line to:

Suggested change
ifisinstance(data, np.ndarray) anddata.ndim==2:
ifhasattr(data, "__array_interface__") andlen(data.__array_interface__.shape)==2:

to support more array-like objects that implements the __array_interface__ protocol (https://numpy.org/doc/stable/reference/arrays.interface.html#object.__array_interface__), but it can be done in a future PR.

@seismanseisman removed the needs review This PR has higher priority and needs review. label Aug 4, 2024
@seismanseisman modified the milestones: 0.13.0, 0.14.0Aug 4, 2024
@seismanseisman removed this from the 0.14.0 milestone Sep 5, 2024
@seisman
seisman marked this pull request as draft September 13, 2024 00:30
@seisman
seisman changed the base branch from main to data_kind/doctestOctober 2, 2024 13:28
@seismanseisman removed the run/benchmark Trigger the benchmark workflow in PRs label Oct 2, 2024
case "file":
file_contexts.append(contextlib.nullcontext(track))
case "matrix":
case "vectors":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pandas.DataFrame now is "vectors" kind.

@seismanseisman added the needs review This PR has higher priority and needs review. label Oct 7, 2024
@seismanseisman added this to the 0.14.0 milestone Oct 7, 2024
@seisman
seisman marked this pull request as ready for review October 7, 2024 05:44
Comment threadpygmt/clib/session.py Outdated
Comment threadpygmt/helpers/utils.py Outdated
Comment on lines 319 to 320
case np.ndarray() if data.ndim == 2: # A 2-D numpy.ndarray object.
kind = "matrix"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  1. Should we extend kind="matrix" to 3-D numpy.ndarray objects?
  2. I'm hesitant to match based on np.ndarray() class only, because while it's not widely advertised, checking for data is not None would have caught other array types implementing the __array_function__ protocol (see NEP18), but checking for isinstance(data, np.ndarray) would exclude those. That said, I'm wondering if the fallback to kind="vectors" might just work, but need to test this out more extensively.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Should we extend kind="matrix" to 3-D numpy.ndarray objects?

It's unclear if we can pass a 3-D numpy array yet. Even if we can, it means more work, since in virtualfile_from_vectors/virtualfile_from_matrix, we explicitly check if the ndim=1 or 2. So, better to revisit the 3-D numpy array support in a separate PR if necessary.

  • I'm hesitant to match based on np.ndarray() class only, because while it's not widely advertised, checking for data is not None would have caught other array types implementing the __array_function__ protocol (see NEP18), but checking for isinstance(data, np.ndarray) would exclude those. That said, I'm wondering if the fallback to kind="vectors" might just work, but need to test this out more extensively.

Yes, I was thinking about checking __array_interface__ in #3351 (comment).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4a4f192, although other array-like objects are not tested.

Comment threadpygmt/clib/session.py
Comment on lines +1822 to +1823
if data.dtype.kind not in "iuf":
_virtualfile_from = self.virtualfile_from_vectors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for these lines?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add a test for it. Before adding more tests, I'm wondering if we should split the big "test_clib_virtualfiles.py" file (with more than 500 lines) into separate smaller test files, i.e., one test file for each Session method.

  • test_clib_virtualfile_in
  • test_clib_virtualfile_from_vectors
  • test_clib_virtualfile_from_matrix
  • test_clib_open_virtualfile

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've split it before in #2784, so yes, ok to split it again 😆

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've split it before in #2784, so yes, ok to split it again 😆

Done in #3512.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test in cfa32ed to cover this line.

Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
z=data[:, 2],
data=data,
)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is added to address #3351 (comment).

This new test passes a string dtype numpy array into GMT C API, which contains longitude/latitude strings. The data kind is "matrix", but since the data dtype is not in iuf, PyGMT will use virtualfile_from_vectors rather than virtualfile_from_matrix to pass the data into GMT C API. Ideally, we should check that the virtualfile_from_vectors is called once and virtualfile_from_matrix is not called, but I find it technically complicated with unittest.mock, so I'll leave it untested.

The function is marked as xfail with GMT 6.5 due to a newly found upstream bug at GenericMappingTools/gmt#8600.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, and thanks for adding the test!

@seismanseisman added the run/test-gmt-dev Trigger the GMT Dev Tests workflow in PR label Oct 15, 2024
@weiji14weiji14 added final review call This PR requires final review and approval from a second reviewer and removed needs review This PR has higher priority and needs review. labels Oct 16, 2024
@seismanseisman removed final review call This PR requires final review and approval from a second reviewer run/test-gmt-dev Trigger the GMT Dev Tests workflow in PR labels Oct 16, 2024
@seisman
seisman merged commit a5c0aa2 into mainOct 16, 2024
@seisman
seisman deleted the refactor/data_kind branch October 16, 2024 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenanceBoring but important stuff for the core devs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@seisman@weiji14