Repository: GenericMappingTools/pygmt

Issue:
Running info on pandas.DataFrame with time column doesn't work
**Description of the problem**

Just noticed that datetime columns being passed into `pygmt.info` doesn't work. This follows on from the `pandas.DataFrame` inputs into `pygmt.info` functionality added in #574, see also #464 and #562 where the datetime machinery should be more or less implemented.

<!-- Please be as detailed as you can when describing an issue. The more information we have, the easier it will be for us to track this down. -->

**Full code that generated the error**

```python
import pygmt
import pandas as pd

table = pd.DataFrame(data=[1,3,2,5,4], columns=["z"])
table["time"] = pd.date_range(start="2020-01-01", periods=5)

pygmt.info(table=table)
```

Note that the equivalent `gmt` command does work on datetime inputs.

```
!gmt info temp.txt
temp.txt: N = 5	<1/5>	<2020-01-01T00:00:00/2020-01-05T00:00:00>
```

**Full error message**

```
---------------------------------------------------------------------------
GMTCLibError                              Traceback (most recent call last)
<ipython-input-6-a9e68c3dc07e> in <module>
----> 1 pygmt.info(table=table)

~/pygmt/pygmt/helpers/decorators.py in new_module(*args, **kwargs)
    235                 if alias in kwargs:
    236                     kwargs[arg] = kwargs.pop(alias)
--> 237             return module_func(*args, **kwargs)
    238 
    239         new_module.aliases = aliases

~/pygmt/pygmt/modules.py in info(table, **kwargs)
    116 
    117         with GMTTempFile() as tmpfile:
--> 118             with file_context as fname:
    119                 arg_str = " ".join(
    120                     [fname, build_arg_string(kwargs), "->" + tmpfile.name]

~/miniconda3/envs/pygmt/lib/python3.7/contextlib.py in __enter__(self)
    110         del self.args, self.kwds, self.func
    111         try:
--> 112             return next(self.gen)
    113         except StopIteration:
    114             raise RuntimeError("generator didn't yield") from None

~/pygmt/pygmt/clib/session.py in virtualfile_from_matrix(self, matrix)
   1268         )
   1269 
-> 1270         self.put_matrix(dataset, matrix)
   1271 
   1272         with self.open_virtual_file(

~/pygmt/pygmt/clib/session.py in put_matrix(self, dataset, matrix, pad)
    906         )
    907         if status != 0:
--> 908             raise GMTCLibError("Failed to put matrix of type {}.".format(matrix.dtype))
    909 
    910     def write_data(self, family, geometry, mode, wesn, output, data):

GMTCLibError: Failed to put matrix of type object.
```

**System information**

Please paste the output of `python -c "import pygmt; pygmt.show_versions()"`:

```
PyGMT information:
  version: v0.1.2+55.g6deb388
System information:
  python: 3.7.8 | packaged by conda-forge | (default, Jul 31 2020, 02:25:08)  [GCC 7.5.0]
  executable: ~/miniconda3/envs/pygmt/bin/python
  machine: Linux-4.19.0-8-amd64-x86_64-with-debian-10.5
Dependency information:
  numpy: 1.19.1
  pandas: 1.1.1
  xarray: 0.16.0
  netCDF4: 1.5.3
  packaging: 20.4
  ghostscript: 9.27
  gmt: None
GMT library information:
  binary dir: ~/miniconda3/envs/pygmt/bin
  cores: 2
  grid layout: rows
  library path: ~/miniconda3/envs/pygmt/lib/libgmt.so
  padding: 2
  plugin dir: ~/miniconda3/envs/pygmt/lib/gmt/plugins
  share dir: ~/miniconda3/envs/pygmt/share/gmt
  version: 6.1.1
```


Discussion:

Patch:
diff --git a/pygmt/modules.py b/pygmt/modules.py
index 1cacf67d..477bc992 100644
--- a/pygmt/modules.py
+++ b/pygmt/modules.py
@@ -78,9 +78,10 @@ def info(table, **kwargs):
 
     Parameters
     ----------
-    table : pandas.DataFrame or np.ndarray or str
-        Either a pandas dataframe, a 1D/2D numpy.ndarray or a file name to an
-        ASCII data table.
+    table : str or np.ndarray or pandas.DataFrame or xarray.Dataset
+        Pass in either a file name to an ASCII data table, a 1D/2D numpy array,
+        a pandas dataframe, or an xarray dataset made up of 1D xarray.DataArray
+        data variables.
     per_column : bool
         Report the min/max values per column in separate columns.
     spacing : str
@@ -107,10 +108,13 @@ def info(table, **kwargs):
         if kind == "file":
             file_context = dummy_context(table)
         elif kind == "matrix":
-            _table = np.asanyarray(table)
-            if table.ndim == 1:  # 1D arrays need to be 2D and transposed
-                _table = np.transpose(np.atleast_2d(_table))
-            file_context = lib.virtualfile_from_matrix(_table)
+            try:
+                # pandas.DataFrame and xarray.Dataset types
+                arrays = [array for _, array in table.items()]
+            except AttributeError:
+                # Python lists, tuples, and numpy ndarray types
+                arrays = np.atleast_2d(np.asanyarray(table).T)
+            file_context = lib.virtualfile_from_vectors(*arrays)
         else:
             raise GMTInvalidInput(f"Unrecognized data type: {type(table)}")
 


Test patch:
diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py
index 142b56dc..92e7616a 100644
--- a/pygmt/tests/test_info.py
+++ b/pygmt/tests/test_info.py
@@ -8,13 +8,17 @@ import numpy.testing as npt
 import pandas as pd
 import pytest
 import xarray as xr
+from packaging.version import Version
 
-from .. import info
+from .. import clib, info
 from ..exceptions import GMTInvalidInput
 
 TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
 POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt")
 
+with clib.Session() as _lib:
+    gmt_version = Version(_lib.info["version"])
+
 
 def test_info():
     "Make sure info works on file name inputs"
@@ -33,7 +37,48 @@ def test_info_dataframe():
     table = pd.read_csv(POINTS_DATA, sep=" ", header=None)
     output = info(table=table)
     expected_output = (
-        "<matrix memory>: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
+        "<vector memory>: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
+    )
+    assert output == expected_output
+
+
+@pytest.mark.xfail(
+    condition=gmt_version <= Version("6.1.1"),
+    reason="UNIX timestamps returned instead of ISO datetime, should work on GMT 6.2.0 "
+    "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved",
+)
+def test_info_pandas_dataframe_time_column():
+    "Make sure info works on pandas.DataFrame inputs with a time column"
+    table = pd.DataFrame(
+        data={
+            "z": [10, 13, 12, 15, 14],
+            "time": pd.date_range(start="2020-01-01", periods=5),
+        }
+    )
+    output = info(table=table)
+    expected_output = (
+        "<vector memory>: N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n"
+    )
+    assert output == expected_output
+
+
+@pytest.mark.xfail(
+    condition=gmt_version <= Version("6.1.1"),
+    reason="UNIX timestamp returned instead of ISO datetime, should work on GMT 6.2.0 "
+    "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved",
+)
+def test_info_xarray_dataset_time_column():
+    "Make sure info works on xarray.Dataset 1D inputs with a time column"
+    table = xr.Dataset(
+        coords={"index": [0, 1, 2, 3, 4]},
+        data_vars={
+            "z": ("index", [10, 13, 12, 15, 14]),
+            "time": ("index", pd.date_range(start="2020-01-01", periods=5)),
+        },
+    )
+    output = info(table=table)
+    expected_output = (
+        "<vector memory>: N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n"
     )
     assert output == expected_output
 
@@ -43,7 +88,7 @@ def test_info_2d_array():
     table = np.loadtxt(POINTS_DATA)
     output = info(table=table)
     expected_output = (
-        "<matrix memory>: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
+        "<vector memory>: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
     )
     assert output == expected_output
 
@@ -51,7 +96,7 @@ def test_info_2d_array():
 def test_info_1d_array():
     "Make sure info works on 1D numpy.ndarray inputs"
     output = info(table=np.arange(20))
-    expected_output = "<matrix memory>: N = 20 <0/19>\n"
+    expected_output = "<vector memory>: N = 20 <0/19>\n"
     assert output == expected_output