Skip to content

Commit a3887ab

Browse files
authored
Wrap blockmedian (#349)
Wrapping the blockmedian function, to be implemented under filtering.py. Original GMT `blockmedian` documentation can be found at https://docs.generic-mapping-tools.org/latest/blockmedian.html. Sample test cases stored under test_blockmedian.py. Current implementation only allows for pandas.DataFrame or ASCII file name inputs, and correspondingly outputs the same type. Tests currently use the load_sample_bathymetry dataset, and we check for a variety of cases that the blockmedian filtered output is valid. Also aliased required arguments spacing (I) and region (R).
1 parent 395e738 commit a3887ab

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Operations on tabular data:
6161
.. autosummary::
6262
:toctree: generated
6363

64+
blockmedian
6465
info
6566
surface
6667

pygmt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# Import modules to make the high-level GMT Python API
1515
from .session_management import begin as _begin, end as _end
1616
from .figure import Figure
17+
from .filtering import blockmedian
1718
from .gridding import surface
1819
from .sampling import grdtrack
1920
from .mathops import makecpt

pygmt/filtering.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
GMT modules for Filtering of 1-D and 2-D Data
3+
"""
4+
import pandas as pd
5+
6+
from .clib import Session
7+
from .exceptions import GMTInvalidInput
8+
from .helpers import (
9+
build_arg_string,
10+
data_kind,
11+
dummy_context,
12+
fmt_docstring,
13+
GMTTempFile,
14+
kwargs_to_strings,
15+
use_alias,
16+
)
17+
18+
19+
@fmt_docstring
20+
@use_alias(I="spacing", R="region")
21+
@kwargs_to_strings(R="sequence")
22+
def blockmedian(table, outfile=None, **kwargs):
23+
"""
24+
Block average (x,y,z) data tables by median estimation.
25+
26+
Reads arbitrarily located (x,y,z) triples [or optionally weighted
27+
quadruples (x,y,z,w)] from a table and writes to the output a median
28+
position and value for every non-empty block in a grid region defined by
29+
the region and spacing arguments.
30+
31+
Full option list at :gmt-docs:`blockmedian.html`
32+
33+
{aliases}
34+
35+
Parameters
36+
----------
37+
table : pandas.DataFrame or str
38+
Either a pandas dataframe with (x, y, z) or (longitude, latitude,
39+
elevation) values in the first three columns, or a file name to an
40+
ASCII data table.
41+
42+
spacing : str
43+
``'xinc[unit][+e|n][/yinc[unit][+e|n]]'``.
44+
x_inc [and optionally y_inc] is the grid spacing.
45+
46+
region : str or list
47+
``'xmin/xmax/ymin/ymax[+r][+uunit]'``.
48+
Specify the region of interest.
49+
50+
outfile : str
51+
Required if 'table' is a file. The file name for the output ASCII file.
52+
53+
Returns
54+
-------
55+
output : pandas.DataFrame or None
56+
Return type depends on whether the outfile parameter is set:
57+
58+
- pandas.DataFrame table with (x, y, z) columns if outfile is not set
59+
- None if outfile is set (filtered output will be stored in outfile)
60+
"""
61+
kind = data_kind(table)
62+
with GMTTempFile(suffix=".csv") as tmpfile:
63+
with Session() as lib:
64+
if kind == "matrix":
65+
if not hasattr(table, "values"):
66+
raise GMTInvalidInput(f"Unrecognized data type: {type(table)}")
67+
file_context = lib.virtualfile_from_matrix(table.values)
68+
elif kind == "file":
69+
if outfile is None:
70+
raise GMTInvalidInput("Please pass in a str to 'outfile'")
71+
file_context = dummy_context(table)
72+
else:
73+
raise GMTInvalidInput(f"Unrecognized data type: {type(table)}")
74+
75+
with file_context as infile:
76+
if outfile is None:
77+
outfile = tmpfile.name
78+
arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile])
79+
lib.call_module(module="blockmedian", args=arg_str)
80+
81+
# Read temporary csv output to a pandas table
82+
if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame
83+
result = pd.read_csv(tmpfile.name, sep="\t", names=table.columns)
84+
elif outfile != tmpfile.name: # return None if outfile set, output in outfile
85+
result = None
86+
87+
return result

pygmt/tests/test_blockmedian.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
Tests for blockmedian
3+
"""
4+
import os
5+
6+
import numpy.testing as npt
7+
import pandas as pd
8+
import pytest
9+
10+
from .. import blockmedian
11+
from ..datasets import load_sample_bathymetry
12+
from ..exceptions import GMTInvalidInput
13+
from ..helpers import data_kind, GMTTempFile
14+
15+
16+
def test_blockmedian_input_dataframe():
17+
"""
18+
Run blockmedian by passing in a pandas.DataFrame as input
19+
"""
20+
dataframe = load_sample_bathymetry()
21+
output = blockmedian(table=dataframe, spacing="5m", region=[245, 255, 20, 30])
22+
assert isinstance(output, pd.DataFrame)
23+
assert all(dataframe.columns == output.columns)
24+
assert output.shape == (5849, 3)
25+
npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0])
26+
27+
return output
28+
29+
30+
def test_blockmedian_wrong_kind_of_input_table_matrix():
31+
"""
32+
Run blockmedian using table input that is not a pandas.DataFrame but still
33+
a matrix
34+
"""
35+
dataframe = load_sample_bathymetry()
36+
invalid_table = dataframe.values
37+
assert data_kind(invalid_table) == "matrix"
38+
with pytest.raises(GMTInvalidInput):
39+
blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30])
40+
41+
42+
def test_blockmedian_wrong_kind_of_input_table_grid():
43+
"""
44+
Run blockmedian using table input that is not a pandas.DataFrame or file
45+
but a grid
46+
"""
47+
dataframe = load_sample_bathymetry()
48+
invalid_table = dataframe.bathymetry.to_xarray()
49+
assert data_kind(invalid_table) == "grid"
50+
with pytest.raises(GMTInvalidInput):
51+
blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30])
52+
53+
54+
def test_blockmedian_input_filename():
55+
"""
56+
Run blockmedian by passing in an ASCII text file as input
57+
"""
58+
with GMTTempFile() as tmpfile:
59+
output = blockmedian(
60+
table="@tut_ship.xyz",
61+
spacing="5m",
62+
region=[245, 255, 20, 30],
63+
outfile=tmpfile.name,
64+
)
65+
assert output is None # check that output is None since outfile is set
66+
assert os.path.exists(path=tmpfile.name) # check that outfile exists at path
67+
output = pd.read_csv(tmpfile.name, sep="\t", header=None)
68+
assert output.shape == (5849, 3)
69+
npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0])
70+
71+
return output
72+
73+
74+
def test_blockmedian_without_outfile_setting():
75+
"""
76+
Run blockmedian by not passing in outfile parameter setting
77+
"""
78+
with pytest.raises(GMTInvalidInput):
79+
blockmedian(table="@tut_ship.xyz", spacing="5m", region=[245, 255, 20, 30])

0 commit comments

Comments
 (0)