Skip to content

Wrap filter1d #1512

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 34 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c8800b7
add and import filter1d.py
willschlitzer Sep 13, 2021
a0e0db3
run make format
willschlitzer Sep 20, 2021
054022c
add test_filter1d
willschlitzer Sep 20, 2021
6e62ec0
change "table" to "data"
willschlitzer Sep 23, 2021
690fec3
raise error if filter not provided
willschlitzer Sep 23, 2021
5e26c22
add tests
willschlitzer Sep 23, 2021
4c55308
add tests
willschlitzer Sep 23, 2021
90867e1
add filter1d to index
willschlitzer Sep 23, 2021
b73f6d8
Merge branch 'main' into wrap-filter1d
willschlitzer Sep 23, 2021
ad21607
add test_filter1d_no_outfile_specified
willschlitzer Sep 25, 2021
78cea4f
update docstring
willschlitzer Sep 29, 2021
7b6c00f
Update pygmt/tests/test_filter1d.py
willschlitzer Nov 7, 2021
f755d2a
remove unused imports
willschlitzer Nov 9, 2021
d667238
Merge branch 'main' into wrap-filter1d
willschlitzer Nov 9, 2021
dbe31dc
Apply suggestions from code review
willschlitzer Nov 20, 2021
af05c18
run make format
willschlitzer Dec 4, 2021
2032ebc
Apply suggestions from code review
willschlitzer Mar 6, 2022
ab85d53
Merge branch 'main' into wrap-filter1d
willschlitzer Mar 6, 2022
a072ec1
add end/filter docstring
willschlitzer Mar 11, 2022
d3cef62
Merge branch 'main' into wrap-filter1d
willschlitzer Mar 11, 2022
b7c0fb8
Merge branch 'main' into wrap-filter1d
willschlitzer Apr 12, 2022
d163d86
[format-command] fixes
actions-bot Apr 12, 2022
ad499a2
Apply suggestions from code review
willschlitzer Apr 13, 2022
1290fbd
Apply suggestions from code review
willschlitzer May 2, 2022
c675c5c
Merge branch 'main' into wrap-filter1d
willschlitzer May 2, 2022
a2b52be
format fix
willschlitzer May 2, 2022
4058b4a
add time_col docstring
willschlitzer May 2, 2022
fea0d1a
Update pygmt/src/filter1d.py
willschlitzer May 2, 2022
215b59b
Merge branch 'main' into wrap-filter1d
seisman May 3, 2022
ac5de72
Apply suggestions from code review
willschlitzer May 3, 2022
0e88c2a
change filter parameter to filter_type
willschlitzer May 5, 2022
01b8252
reduce time_col parameters to only int
willschlitzer May 5, 2022
d677629
fix filter1d tests
willschlitzer May 5, 2022
a0ada18
Merge branch 'main' into wrap-filter1d
willschlitzer May 5, 2022
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
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Operations on tabular data
blockmean
blockmedian
blockmode
filter1d
nearneighbor
project
select
Expand Down
1 change: 1 addition & 0 deletions pygmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
blockmode,
config,
dimfilter,
filter1d,
grd2cpt,
grd2xyz,
grdclip,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pygmt.src.config import config
from pygmt.src.contour import contour
from pygmt.src.dimfilter import dimfilter
from pygmt.src.filter1d import filter1d
from pygmt.src.grd2cpt import grd2cpt
from pygmt.src.grd2xyz import grd2xyz
from pygmt.src.grdclip import grdclip
Expand Down
145 changes: 145 additions & 0 deletions pygmt/src/filter1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
filter1d - Time domain filtering of 1-D data tables
"""
import warnings

import pandas as pd
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import GMTTempFile, build_arg_string, fmt_docstring, use_alias


@fmt_docstring
@use_alias(
E="end",
F="filter_type",
N="time_col",
)
def filter1d(data, output_type="pandas", outfile=None, **kwargs):
r"""
Time domain filtering of 1-D data tables.

A general time domain filter for multiple column time
series data. The user specifies which column is the time (i.e., the
independent variable) via ``time_col``. The fastest operation
occurs when the input time series are equally spaced and have no gaps or
outliers and the special options are not needed.
Read a table and output as a :class:`numpy.ndarray`,
:class:`pandas.DataFrame`, or ASCII file.

Full option list at :gmt-docs:`filter1d.html`

{aliases}

Parameters
----------
filter_type : str
**type**\ *width*\ [**+h**].
Sets the filter **type**. Choose among convolution and non-convolution
filters. Append the filter code followed by the full filter
*width* in same units as time column. By default, this
performs a low-pass filtering; append **+h** to select high-pass
filtering. Some filters allow for optional arguments and a modifier.

Available convolution filter types are:

- (**b**) Boxcar: All weights are equal.
- (**c**) Cosine Arch: Weights follow a cosine arch curve.
- (**g**) Gaussian: Weights are given by the Gaussian function.
- (**f**) Custom: Instead of *width* give name of a one-column file
with your own weight coefficients.

Non-convolution filter types are:

- (**m**) Median: Returns median value.
- (**p**) Maximum likelihood probability (a mode estimator): Return
modal value. If more than one mode is found we return their average
value. Append **+l** or **+u** if you rather want
to return the lowermost or uppermost of the modal values.
- (**l**) Lower: Return the minimum of all values.
- (**L**) Lower: Return minimum of all positive values only.
- (**u**) Upper: Return maximum of all values.
- (**U**) Upper: Return maximum of all negative values only.

Upper case type **B**, **C**, **G**, **M**, **P**, **F** will use
robust filter versions: i.e., replace outliers (2.5 L1 scale off
median, using 1.4826 \* median absolute deviation [MAD]) with median
during filtering.

In the case of **L**\|\ **U** it is possible that no data passes
the initial sign test; in that case the filter will return 0.0.
Apart from custom coefficients (**f**), the other filters may accept
variable filter widths by passing *width* as a two-column time-series
file with filter widths in the second column. The filter-width file
does not need to be co-registered with the data as we obtain the
required filter width at each output location via interpolation. For
multi-segment data files the filter file must either have the same
number of segments or just a single segment to be used for all data
segments.

end : bool
Include ends of time series in output. The default [False] loses
half the filter-width of data at each end.

time_col : int
Indicates which column contains the independent variable (time). The
left-most column is 0, while the right-most is (*n_cols* - 1)
[Default is 0].

output_type : str
Determine the format the xyz data will be returned in [Default is
``pandas``]:

- ``numpy`` - :class:`numpy.ndarray`
- ``pandas``- :class:`pandas.DataFrame`
- ``file`` - ASCII file (requires ``outfile``)
outfile : str
The file name for the output ASCII file.

Returns
-------
ret : pandas.DataFrame or numpy.ndarray or None
Return type depends on ``outfile`` and ``output_type``:

- None if ``outfile`` is set (output will be stored in file set by
``outfile``)
- :class:`pandas.DataFrame` or :class:`numpy.ndarray` if ``outfile`` is
not set (depends on ``output_type`` [Default is
:class:`pandas.DataFrame`])

"""
if kwargs.get("F") is None:
raise GMTInvalidInput("Pass a required argument to 'filter_type'.")
if output_type not in ["numpy", "pandas", "file"]:
raise GMTInvalidInput("Must specify format as either numpy, pandas, or file.")
if outfile is not None and output_type != "file":
msg = (
f"Changing `output_type` of filter1d from '{output_type}' to 'file' "
"since `outfile` parameter is set. Please use `output_type='file'` "
"to silence this warning."
)
warnings.warn(msg, category=RuntimeWarning, stacklevel=2)
output_type = "file"
elif output_type == "file" and outfile is None:
raise GMTInvalidInput("Must specify outfile for ASCII output.")

with GMTTempFile() as tmpfile:
with Session() as lib:
file_context = lib.virtualfile_from_data(check_kind="vector", data=data)
with file_context as infile:
if outfile is None:
outfile = tmpfile.name
lib.call_module(
module="filter1d",
args=build_arg_string(kwargs, infile=infile, outfile=outfile),
)

# Read temporary csv output to a pandas table
if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame
result = pd.read_csv(tmpfile.name, sep="\t", comment=">")
elif outfile != tmpfile.name: # return None if outfile set, output in outfile
result = None

if output_type == "numpy":
result = result.to_numpy()
return result
96 changes: 96 additions & 0 deletions pygmt/tests/test_filter1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Tests for filter1d.
"""

import os

import numpy as np
import pandas as pd
import pytest
from pygmt import filter1d
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import GMTTempFile
from pygmt.src import which


@pytest.fixture(scope="module", name="data")
def fixture_table():
"""
Load the grid data from the sample earth_relief file.
"""
fname = which("@MaunaLoa_CO2.txt", download="c")
data = pd.read_csv(
fname, header=None, skiprows=1, sep=r"\s+", names=["date", "co2_ppm"]
)
return data


def test_filter1d_no_outfile(data):
"""
Test filter1d with no set outgrid.
"""
result = filter1d(data=data, filter_type="g5")
assert result.shape == (670, 2)


def test_filter1d_file_output(data):
"""
Test that filter1d returns a file output when it is specified.
"""
with GMTTempFile(suffix=".txt") as tmpfile:
result = filter1d(
data=data, filter_type="g5", outfile=tmpfile.name, output_type="file"
)
assert result is None # return value is None
assert os.path.exists(path=tmpfile.name) # check that outfile exists


def test_filter1d_invalid_format(data):
"""
Test that filter1d fails with an incorrect format for output_type.
"""
with pytest.raises(GMTInvalidInput):
filter1d(data=data, filter_type="g5", output_type="a")


def test_filter1d_no_filter(data):
"""
Test that filter1d fails with an argument is missing for filter.
"""
with pytest.raises(GMTInvalidInput):
filter1d(data=data)


def test_filter1d_no_outfile_specified(data):
"""
Test that filter1d fails when outpput_type is set to 'file' but no output
file name is specified.
"""
with pytest.raises(GMTInvalidInput):
filter1d(data=data, filter_type="g5", output_type="file")


def test_filter1d_outfile_incorrect_output_type(data):
"""
Test that filter1d raises a warning when an outfile filename is set but the
output_type is not set to 'file'.
"""
with pytest.warns(RuntimeWarning):
with GMTTempFile(suffix=".txt") as tmpfile:
result = filter1d(
data=data, filter_type="g5", outfile=tmpfile.name, output_type="numpy"
)
assert result is None # return value is None
assert os.path.exists(path=tmpfile.name) # check that outfile exists


def test_filter1d_format(data):
"""
Test that correct formats are returned.
"""
time_series_default = filter1d(data=data, filter_type="g5")
assert isinstance(time_series_default, pd.DataFrame)
time_series_array = filter1d(data=data, filter_type="g5", output_type="numpy")
assert isinstance(time_series_array, np.ndarray)
time_series_df = filter1d(data=data, filter_type="g5", output_type="pandas")
assert isinstance(time_series_df, pd.DataFrame)