Skip to content
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

Support various datetime types as input #464

Merged
merged 18 commits into from
Jul 13, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
64 changes: 64 additions & 0 deletions pygmt/clib/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Functions to convert data types into ctypes friendly formats.
"""
import numpy as np
import pandas as pd

from ..exceptions import GMTInvalidInput

Expand Down Expand Up @@ -237,3 +238,66 @@ def kwargs_to_ctypes_array(argument, kwargs, dtype):
if argument in kwargs:
return dtype(*kwargs[argument])
return None


def array_to_datetime(array):
"""
Convert an 1d datetime array from various types into pandas.DatetimeIndex
(i.e., numpy.datetime64).

If the input array is not in legal datetime formats, raise a "ParseError"
exception.

Parameters
----------
array : list or 1d array
The input datetime array in various formats.

Supported types:

- str
- numpy.datetime64
- pandas.DateTimeIndex
- datetime.datetime and datetime.date

Returns
-------
array : 1d datetime array in pandas.DatetimeIndex (i.e., numpy.datetime64)

Examples
--------
>>> import datetime
>>> # numpy.datetime64 array
>>> x = np.array(["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"],
... dtype="datetime64")
>>> array_to_datetime(x)
DatetimeIndex(['2010-06-01 00:00:00', '2011-06-01 12:00:00',
'2012-01-01 12:34:56'],
dtype='datetime64[ns]', freq=None)
>>> # pandas.DateTimeIndex array
>>> x = pd.date_range("2013", freq="YS", periods=3)
>>> array_to_datetime(x)
DatetimeIndex(['2013-01-01', '2014-01-01', '2015-01-01'],
dtype='datetime64[ns]', freq='AS-JAN')
>>> # Python's built-in date and datetime
>>> x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)]
>>> array_to_datetime(x)
DatetimeIndex(['2018-01-01', '2019-01-01'], dtype='datetime64[ns]',
freq=None)
>>> # Raw datetime strings in various format
>>> x = ['2018', "2018-02", "2018-03-01", "2018-04-01T01:02:03",
... "5/1/2018", "Jun 05, 2018", "2018/07/02"]
>>> array_to_datetime(x)
DatetimeIndex(['2018-01-01 00:00:00', '2018-02-01 00:00:00',
'2018-03-01 00:00:00', '2018-04-01 01:02:03',
'2018-05-01 00:00:00', '2018-06-05 00:00:00',
'2018-07-02 00:00:00'],
dtype='datetime64[ns]', freq=None)
>>> # Mixed datetime types
>>> x = ['2018-01-01', np.datetime64('2018-01-01'),
... datetime.datetime(2018, 1, 1)]
>>> array_to_datetime(x)
DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'],
dtype='datetime64[ns]', freq=None)
"""
return pd.to_datetime(array)
39 changes: 27 additions & 12 deletions pygmt/clib/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
vectors_to_arrays,
dataarray_to_matrix,
as_c_contiguous,
array_to_datetime,
)

FAMILIES = [
Expand All @@ -48,12 +49,13 @@
REGISTRATIONS = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"]

DTYPES = {
"float64": "GMT_DOUBLE",
"float32": "GMT_FLOAT",
"int64": "GMT_LONG",
"int32": "GMT_INT",
"uint64": "GMT_ULONG",
"uint32": "GMT_UINT",
np.float64: "GMT_DOUBLE",
np.float32: "GMT_FLOAT",
np.int64: "GMT_LONG",
np.int32: "GMT_INT",
np.uint64: "GMT_ULONG",
np.uint32: "GMT_UINT",
np.datetime64: "GMT_DATETIME",
Copy link
Member

Choose a reason for hiding this comment

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

Quick question, is there a GMT dtype for string types? I can't seem to find one at https://docs.generic-mapping-tools.org/6.1/api.html#gmt-c-api.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a GMT_TEXT, but it may not related to the GMT_Put_Strings function.

}


Expand Down Expand Up @@ -708,15 +710,22 @@ def _check_dtype_and_dim(self, array, ndim):
True

"""
if array.dtype.name not in DTYPES:
raise GMTInvalidInput(
"Unsupported numpy data type '{}'.".format(array.dtype.name)
)
# check the array has the given dimension
if array.ndim != ndim:
raise GMTInvalidInput(
"Expected a numpy 1d array, got {}d.".format(array.ndim)
)
return self[DTYPES[array.dtype.name]]

# check the array has a valid/known data type
if array.dtype.type not in DTYPES:
try:
# Try to convert any unknown numpy data types to np.datetime64
array = np.asarray(array, dtype=np.datetime64)
except ValueError:
raise GMTInvalidInput(
"Unsupported numpy data type '{}'.".format(array.dtype.type)
)
return self[DTYPES[array.dtype.type]]

def put_vector(self, dataset, column, vector):
"""
Expand Down Expand Up @@ -762,7 +771,13 @@ def put_vector(self, dataset, column, vector):
)

gmt_type = self._check_dtype_and_dim(vector, ndim=1)
vector_pointer = vector.ctypes.data_as(ctp.c_void_p)
if gmt_type == self["GMT_DATETIME"]:
vector_pointer = (ctp.c_char_p * len(vector))()
vector_pointer[:] = np.char.encode(
np.datetime_as_string(array_to_datetime(vector))
)
else:
vector_pointer = vector.ctypes.data_as(ctp.c_void_p)
status = c_put_vector(
self.session_pointer, dataset, column, gmt_type, vector_pointer
)
Expand Down
Binary file added pygmt/tests/baseline/test_plot_datetime.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions pygmt/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"""
Tests plot.
"""
import datetime
import os

import numpy as np
import pandas as pd
import xarray as xr

import pytest

from .. import Figure
Expand Down Expand Up @@ -284,3 +288,38 @@ def test_plot_scalar_xy():
fig.plot(x=0, y=0, style="t1c")
fig.plot(x=1.5, y=-1.5, style="s1c")
return fig


@pytest.mark.mpl_image_compare
def test_plot_datetime():
"""Test various datetime input data"""
fig = Figure()
fig.basemap(projection="X15c/5c", region="2010-01-01/2020-01-01/0/10", frame=True)

# numpy.datetime64 types
x = np.array(
["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], dtype="datetime64"
)
y = [1.0, 2.0, 3.0]
fig.plot(x, y, style="c0.2c", pen="1p")

# pandas.DatetimeIndex
x = pd.date_range("2013", freq="YS", periods=3)
y = [4, 5, 6]
fig.plot(x, y, style="t0.2c", pen="1p")

# xarray.DataArray
x = xr.DataArray(data=pd.date_range(start="2015-03", freq="QS", periods=3))
y = [7.5, 6, 4.5]
fig.plot(x, y, style="s0.2c", pen="1p")

# raw datetime strings
x = ["2016-02-01", "2017-03-04T00:00"]
y = [7, 8]
fig.plot(x, y, style="a0.2c", pen="1p")

# the Python built-in datetime and date
x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)]
y = [8.5, 9.5]
fig.plot(x, y, style="i0.2c", pen="1p")
return fig