Skip to content

Figure.meca: Fix beachball offsetting with dict/pandas inputs #2202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions pygmt/src/meca.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,11 @@ def meca(
Depth(s) of event location in kilometers. Must be the same length as
the number of events. Will override the ``depth`` values in ``spec``
if ``spec`` is a dict or pd.DataFrame.
plot_longitude: int, float, list, or 1d numpy array
plot_longitude: int, float, str, list, or 1d numpy array
Longitude(s) at which to place beachball. Must be the same length as
the number of events. Will override the ``plot_longitude`` values in
``spec`` if ``spec`` is a dict or pd.DataFrame.
plot_latitude: int, float, list, or 1d numpy array
plot_latitude: int, float, str, list, or 1d numpy array
Latitude(s) at which to place beachball. List must be the same length
as the number of events. Will override the ``plot_latitude`` values in
``spec`` if ``spec`` is a dict or pd.DataFrame.
Expand Down Expand Up @@ -272,10 +272,10 @@ def meca(
spec["latitude"] = np.atleast_1d(latitude)
if depth is not None:
spec["depth"] = np.atleast_1d(depth)
if plot_longitude is not None: # must be in string type
spec["plot_longitude"] = np.atleast_1d(plot_longitude).astype(str)
if plot_latitude is not None: # must be in string type
spec["plot_latitude"] = np.atleast_1d(plot_latitude).astype(str)
if plot_longitude is not None:
spec["plot_longitude"] = np.atleast_1d(plot_longitude)
if plot_latitude is not None:
spec["plot_latitude"] = np.atleast_1d(plot_latitude)
if event_name is not None:
spec["event_name"] = np.atleast_1d(event_name).astype(str)

Expand All @@ -293,9 +293,13 @@ def meca(
newcols = ["longitude", "latitude", "depth"] + param_conventions[convention]
if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns:
newcols += ["plot_longitude", "plot_latitude"]
spec[["plot_longitude", "plot_latitude"]] = spec[
["plot_longitude", "plot_latitude"]
].astype(str)
kwargs["A"] = True
if "event_name" in spec.columns:
newcols += ["event_name"]
spec["event_name"] = spec["event_name"].astype(str)
# reorder columns in DataFrame
spec = spec.reindex(newcols, axis=1)
elif isinstance(spec, np.ndarray) and spec.ndim == 1:
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I was re-reading #1129 again and was gonna say, it's a little surprising that a 2D numpy array passed in with plot_longitude and plot_latitude columns (stored in float) works (since the test_meca_spec_2d_array unit test is passing fine). I guess the GMT C code is smart enough with handling data from virtualfile_from_matrix vs virtualfile_from_vectors.

Nothing to do here, just a random comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

it's a little surprising that a 2D numpy array passed in with plot_longitude and plot_latitude columns (stored in float) works (since the test_meca_spec_2d_array unit test is passing fine).

That's a good point. I think it shouldn't work. The test_meca_spec_2d_array test passes because both plot_longitude and plot_latitude are 0 (which means no offsetting due to backward compatibility issues).

I'll find some time and see if offsetting using a 2D numpy array works, and if not, will try to find a fix.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yes, so we should probably update the test to use a non-zero offset. I actually wanted to parse all the user inputs into a pandas.DataFrame (which can hold different dtypes like int, str, float) in #1613 (comment) which meant everything goes through virtualfile_from_vectors, but that was superseded by #1784 that maintained two separate paths going via virtualfile_from_vectors for dict/pd.DataFrame or virtualfile_from_matrix for 2D np.ndarray inputs. Just as an idea 🙂

If this ends up getting too complicated, we can merge in this PR first to fix offsets for dict/pandas inputs, and have a separate one to handle offsets for 2D numpy array inputs.

Copy link
Member Author

Choose a reason for hiding this comment

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

If this ends up getting too complicated, we can merge in this PR first to fix offsets for dict/pandas inputs, and have a separate one to handle offsets for 2D numpy array inputs.

I prefer to work on it in a separate PR.

Expand Down
28 changes: 28 additions & 0 deletions pygmt/tests/test_meca.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,34 @@ def test_meca_dict_offset():
return fig


@pytest.mark.mpl_image_compare(filename="test_meca_dict_offset.png")
def test_meca_dict_offset_in_dict():
"""
Test offsetting beachballs for a dict input with offset parameters in the
dict.

See https://github.com/GenericMappingTools/pygmt/issues/2016.
"""
fig = Figure()
focal_mechanism = dict(
strike=330,
dip=30,
rake=90,
magnitude=3,
plot_longitude=-124.5,
plot_latitude=47.5,
)
fig.basemap(region=[-125, -122, 47, 49], projection="M6c", frame=True)
fig.meca(
spec=focal_mechanism,
scale="1c",
longitude=-124,
latitude=48,
depth=12.0,
)
return fig


@pytest.mark.mpl_image_compare
def test_meca_dict_eventname():
"""
Expand Down