Repository: GenericMappingTools/pygmt

Issue:
Return values of gmt.info
This issue is opened to further discuss the https://github.com/GenericMappingTools/gmt-python/pull/106#issuecomment-363649758 about `gmt.info`.

> Do you think we should print the output instead of returning it? There could be a flag to trigger a return instead of printing.

IMHO, returning the output makes more sense than printing it. It's easy for users to print it via `print(gmt.info())` if they need.

> Either way, if the returned string is just a list of numbers, it would be better to return an array/list of the number instead.

As far as I know, the outputs of `gmt info` can be divided into following types when different options are used:

**Type 1**
```
$ gmt info points.txt 
points.txt: N = 20	<11.5309/61.7074>	<-2.9289/7.8648>	<0.1412/0.9338>
```
I think this type is useless in most cases. We can leave it as it is.

**Type 2**
```
$ gmt info points.txt -C
11.5309	61.7074	-2.9289	7.8648	0.1412	0.9338
```
We can convert this type to a list/array.

**Type 3**
```
$ gmt info points.txt -I0.1                                                            
-R11.5/61.8/-3/7.9
```
`-Rw/e/s/n` is useless for Python. Should be convert to a list/array.

**Type 4**
```
$ gmt info points.txt -T0.1
-T11.5/61.8/0.1
```
Useless. Should be convert to a list/array.

**Types 5**
```
$ gmt info points.txt -Ib   
11.5309	-2.9289
61.7074	-2.9289
61.7074	7.8648
11.5309	7.8648
11.5309	-2.9289
```
`gmt info -Ib` reports the bounding box polygon for the data files, which is usually used in further `fig.plot()`. So it may make more sense to return it as two arrays `x` and `y`. A much general way is to return it as a ndarray, so users can access the first column via `output[:,0]` and the ith column via `output[:,i-1]`.

If `-Af|s` option is used, the output becomes more complicated, e.g.:
```
$ gmt info points.txt points-copy.txt -C -Af
11.5309	61.7074	-2.9289	7.8648	0.1412	0.9338
11.5309	61.7074	-2.9289	7.8648	0.1412	0.9338
```
In such cases, we can return the output as a ndarray.

**Conclusion**

What we can do with the `gmt.info` outputs:
 
1. convert it to an array (type 2-4)
2. convert it to a multi-dimensional ndarray (type 2-5 and some more complicated cases).
3. leave the output as it is (type 1 and other cases not discussed in the issue).


Discussion:
weiji14: Cool, thanks for reviewing, this will be a great addition for v0.2.0! I'll temporarily disable the required Windows Py3.8 test (random unrelated failure we should investigate at some point) to merge this.

Patch:
diff --git a/pygmt/modules.py b/pygmt/modules.py
index 94512ec3..1cacf67d 100644
--- a/pygmt/modules.py
+++ b/pygmt/modules.py
@@ -60,14 +60,17 @@ def info(table, **kwargs):
     """
     Get information about data tables.
 
-    Reads from files and finds the extreme values in each of the columns.
-    It recognizes NaNs and will print warnings if the number of columns vary
-    from record to record. As an option, it will find the extent of the first
-    n columns rounded up and down to the nearest multiple of the supplied
-    increments. By default, this output will be in the form *-Rw/e/s/n*,
-    or the output will be in column form for as many columns as there are
-    increments provided. The *nearest_multiple* option will provide a
-    *-Tzmin/zmax/dz* string for makecpt.
+    Reads from files and finds the extreme values in each of the columns
+    reported as min/max pairs. It recognizes NaNs and will print warnings if
+    the number of columns vary from record to record. As an option, it will
+    find the extent of the first two columns rounded up and down to the nearest
+    multiple of the supplied increments given by *spacing*. Such output will be
+    in a numpy.ndarray form ``[w, e, s, n]``, which can be used directly as the
+    *region* argument for other modules (hence only dx and dy are needed). If
+    the *per_column* option is combined with *spacing*, then the numpy.ndarray
+    output will be rounded up/down for as many columns as there are increments
+    provided in *spacing*. A similar option *nearest_multiple* option will
+    provide a numpy.ndarray in the form of ``[zmin, zmax, dz]`` for makecpt.
 
     Full option list at :gmt-docs:`gmtinfo.html`
 
@@ -83,12 +86,21 @@ def info(table, **kwargs):
     spacing : str
         ``'[b|p|f|s]dx[/dy[/dz...]]'``.
         Report the min/max of the first n columns to the nearest multiple of
-        the provided increments and output results in the form *-Rw/e/s/n*
-        (unless *per_column* is set).
+        the provided increments and output results in the form
+        ``[w, e, s, n]``.
     nearest_multiple : str
         ``'dz[+ccol]'``
         Report the min/max of the first (0'th) column to the nearest multiple
-        of dz and output this as the string *-Tzmin/zmax/dz*.
+        of dz and output this in the form ``[zmin, zmax, dz]``.
+
+    Returns
+    -------
+    output : np.ndarray or str
+        Return type depends on whether any of the 'per_column', 'spacing', or
+        'nearest_multiple' parameters are set.
+
+        - np.ndarray if either of the above parameters are used.
+        - str if none of the above parameters are used.
     """
     kind = data_kind(table)
     with Session() as lib:
@@ -108,7 +120,16 @@ def info(table, **kwargs):
                     [fname, build_arg_string(kwargs), "->" + tmpfile.name]
                 )
                 lib.call_module("info", arg_str)
-            return tmpfile.read()
+            result = tmpfile.read()
+
+        if any(arg in kwargs for arg in ["C", "I", "T"]):
+            # Converts certain output types into a numpy array
+            # instead of a raw string that is less useful.
+            if result.startswith(("-R", "-T")):  # e.g. -R0/1/2/3 or -T0/9/1
+                result = result[2:].replace("/", " ")
+            result = np.loadtxt(result.splitlines())
+
+        return result
 
 
 @fmt_docstring


Test patch:
diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py
index b7eadc53..142b56dc 100644
--- a/pygmt/tests/test_info.py
+++ b/pygmt/tests/test_info.py
@@ -4,6 +4,7 @@ Tests for gmtinfo
 import os
 
 import numpy as np
+import numpy.testing as npt
 import pandas as pd
 import pytest
 import xarray as xr
@@ -57,25 +58,42 @@ def test_info_1d_array():
 def test_info_per_column():
     "Make sure the per_column option works"
     output = info(table=POINTS_DATA, per_column=True)
-    assert output == "11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338\n"
+    npt.assert_allclose(
+        actual=output, desired=[11.5309, 61.7074, -2.9289, 7.8648, 0.1412, 0.9338]
+    )
 
 
 def test_info_spacing():
     "Make sure the spacing option works"
     output = info(table=POINTS_DATA, spacing=0.1)
-    assert output == "-R11.5/61.8/-3/7.9\n"
+    npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9])
+
+
+def test_info_spacing_bounding_box():
+    "Make sure the spacing option for writing a bounding box works"
+    output = info(table=POINTS_DATA, spacing="b")
+    npt.assert_allclose(
+        actual=output,
+        desired=[
+            [11.5309, -2.9289],
+            [61.7074, -2.9289],
+            [61.7074, 7.8648],
+            [11.5309, 7.8648],
+            [11.5309, -2.9289],
+        ],
+    )
 
 
 def test_info_per_column_spacing():
     "Make sure the per_column and spacing options work together"
     output = info(table=POINTS_DATA, per_column=True, spacing=0.1)
-    assert output == "11.5 61.8 -3 7.9 0.1412 0.9338\n"
+    npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9, 0.1412, 0.9338])
 
 
 def test_info_nearest_multiple():
     "Make sure the nearest_multiple option works"
     output = info(table=POINTS_DATA, nearest_multiple=0.1)
-    assert output == "-T11.5/61.8/0.1\n"
+    npt.assert_allclose(actual=output, desired=[11.5, 61.8, 0.1])
 
 
 def test_info_fails():