Repository: GenericMappingTools/pygmt

Issue:
Bug with x2sys_cross module not handling NaN values properly
**Description of the problem**
When I using the x2sys_cross function, passing a pandas.DataFrame I got no result return. However, passing an ASCII file the the program works fine. I don't know what went wrong, and I  tested the code of pull request#591 it worked.
I upload the **ssh.txt** for running the above code. See my **testdata repertory**. https://github.com/isLiYang/testdata

I hope someone can solve my question.

Thank You.

**Full code that generated the error**

```python
import numpy as np
import pandas as pd
import pygmt
import os
from tempfile import TemporaryDirectory

# load my data.
data = np.loadtxt('ssh.txt')
df = pd.DataFrame({
    'lon': data[:, 0],
    'lat': data[:, 1],
    'ssh': data[:, 2]
})
df.columns = ['lon', 'lat', 'z']


# load test data from: Pull request #591.
# https://github.com/GenericMappingTools/pygmt/pull/591
dataframe: pd.DataFrame = pygmt.datasets.load_sample_bathymetry()
dataframe.columns = ["x", "y", "z"]  # longitude, latitude, bathymetry


# Test plll resuest #591, it works fine.
os.environ["X2SYS_HOME"] = os.getcwd()

with TemporaryDirectory(prefix="X2SYS", dir=os.environ["X2SYS_HOME"]) as tmpdir:
    tag = os.path.basename(tmpdir)
    pygmt.x2sys_init(tag=tag, fmtfile="xyz", suffix="xyz", force=True)
    output: pd.DataFrame = pygmt.x2sys_cross(tracks=[dataframe], tag=tag, coe="i", verbose="i")


# test my data.
# 1.When passing a ascii file, the calculation is fine and the ascii file and the dataframe object can be output.
os.environ["X2SYS_HOME"] = os.getcwd()
with TemporaryDirectory(prefix="X2SYS", dir=os.environ["X2SYS_HOME"]) as tmpdir:
    tag = os.path.basename(tmpdir)
    # passing file.
    pygmt.x2sys_init(tag=tag, fmtfile='geoz', suffix='txt', force=True, discontinuity='g', gap=['d100'], verbose='i')
    pygmt.x2sys_cross(tracks=['ssh.txt'], outfile='pyxovers1.dat', tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)
    xovers1 = pygmt.x2sys_cross(tracks=['ssh.txt'], tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)


# 2.When passing a dataframe, it will not output the result of the calculation or report an error.
os.environ["X2SYS_HOME"] = os.getcwd()
with TemporaryDirectory(prefix="X2SYS", dir=os.environ["X2SYS_HOME"]) as tmpdir:
    tag = os.path.basename(tmpdir)
    # passing file.
    pygmt.x2sys_init(tag=tag, fmtfile='geoz', force=True, discontinuity='g', gap=['d100'], verbose='i')
    pygmt.x2sys_cross(tracks=[df], outfile='pyxovers2.dat', tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)
    xovers2 = pygmt.x2sys_cross(tracks=[df], tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)

print('finished...')


```

**Full error message**
**pyxovers2.dat** will be created, but no data in there. 
below are message of 
```python
pygmt.x2sys_cross(tracks=[df], outfile='pyxovers2.dat', tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)
```
and
```python
    xovers2 = pygmt.x2sys_cross(tracks=[df], tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)
```
```
x2sys_cross [INFORMATION]: Files found: 1
x2sys_cross [INFORMATION]: Checking for duplicates :
x2sys_cross [INFORMATION]: 0 found
x2sys_cross [WARNING]: No time column, use dummy times
x2sys_cross [INFORMATION]: Writing Data Table to file pyxovers2.dat
x2sys_cross [INFORMATION]: Files found: 1
x2sys_cross [INFORMATION]: Checking for duplicates :
x2sys_cross [INFORMATION]: 0 found
x2sys_cross [WARNING]: No time column, use dummy times
x2sys_cross [INFORMATION]: Writing Data Table to file /tmp/pygmt-4qfsz7oy.txt
Traceback (most recent call last):
  File "/mnt/e/HY2A/tmp.py", line 50, in <module>
    xovers2 = pygmt.x2sys_cross(tracks=[df], tag=tag, interpolation='a', verbose='i', region='g', trackvalues=True)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pygmt/helpers/decorators.py", line 414, in new_module
    return module_func(*args, **kwargs)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pygmt/helpers/decorators.py", line 557, in new_module
    return module_func(*args, **kwargs)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pygmt/src/x2sys_cross.py", line 228, in x2sys_cross
    table = pd.read_csv(
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pandas/io/parsers.py", line 610, in read_csv
    return _read(filepath_or_buffer, kwds)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pandas/io/parsers.py", line 462, in _read
    parser = TextFileReader(filepath_or_buffer, **kwds)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pandas/io/parsers.py", line 819, in __init__
    self._engine = self._make_engine(self.engine)
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pandas/io/parsers.py", line 1050, in _make_engine
    return mapping[engine](self.f, **self.options)  # type: ignore[call-arg]
  File "/home/liyang/anaconda3/envs/pygmt/lib/python3.9/site-packages/pandas/io/parsers.py", line 1873, in __init__
    self._reader = parsers.TextReader(self.handles.handle, **kwds)
  File "pandas/_libs/parsers.pyx", line 521, in pandas._libs.parsers.TextReader.__cinit__
pandas.errors.EmptyDataError: No columns to parse from file

Process finished with exit code 1

```

**System information**
```
PyGMT information:
  version: v0.4.0
System information:
  python: 3.9.5 (default, Jun  4 2021, 12:28:51)  [GCC 7.5.0]
  executable: /home/liyang/anaconda3/envs/pygmt/bin/python
  machine: Linux-4.4.0-19041-Microsoft-x86_64-with-glibc2.31
Dependency information:
  numpy: 1.20.2
  pandas: 1.2.5
  xarray: 0.18.2
  netCDF4: 1.5.7
  packaging: 20.9
  ghostscript: 9.54.0
  gmt: 6.2.0
GMT library information:
  binary dir: /home/liyang/anaconda3/envs/pygmt/bin
  cores: 8
  grid layout: rows
  library path: /home/liyang/anaconda3/envs/pygmt/lib/libgmt.so
  padding: 2
  plugin dir: /home/liyang/anaconda3/envs/pygmt/lib/gmt/plugins
  share dir: /home/liyang/anaconda3/envs/pygmt/share/gmt
  version: 6.2.0
```



Discussion:

Patch:
diff --git a/doc/_static/style.css b/doc/_static/style.css
index d8171f6f..aec481ce 100644
--- a/doc/_static/style.css
+++ b/doc/_static/style.css
@@ -199,3 +199,37 @@ a.copybtn {
 .sphx-glr-single-img {
     max-width: 80%!important;
 }
+
+.team-row {
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.team-card {
+    background-color: #fff;
+    background-clip: border-box;
+    border: 1px solid rgba(0,0,0,.125);
+    border-radius: 0.5rem;
+    margin: 0.25em;
+    flex: 0 1 175px;
+}
+
+.team-img {
+    border-radius: 0.5rem;
+    width: 100%;
+    height: 15vh!important;
+    object-fit: cover;
+}
+
+.team-card-body {
+    padding: 1.25rem;
+    height: 100px;
+}
+
+.team-card-text {
+    display: flex;
+    margin-block-start: 0em;
+    margin-block-end: 0.2em;
+    margin-inline-start: 0px;
+    margin-inline-end: 0px;
+}
diff --git a/doc/index.rst b/doc/index.rst
index f3e025b4..88ffce2a 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -58,5 +58,12 @@
 
     api/index.rst
     changes.md
+
+.. toctree::
+    :maxdepth: 2
+    :hidden:
+    :caption: Development
+
+    team.md
     contributing.md
     maintenance.md
diff --git a/doc/team.md b/doc/team.md
new file mode 100644
index 00000000..30236584
--- /dev/null
+++ b/doc/team.md
@@ -0,0 +1,99 @@
+# Team Gallery
+
+We are an international team dedicated to building a Pythonic API for the
+Generic Mapping Tools (GMT). Our goal is to improve GMT's accessibility for
+new and experienced users by creating user-friendly interfaces with the GMT
+C API, supporting rich display in Jupyter notebooks, and integrating with
+the PyData ecosystem.
+
+All are welcome to become involved with the PyGMT project! For more information
+about how to get involved, see the {doc}`contributing`.
+
+## Distinguished Contributors
+
+PyGMT Distinguished Contributors are recognized for their substantial
+contributions to PyGMT, which may include code, documentation, pull request
+review, triaging, forum responses, community building and engagement,
+outreach, and inclusion and diversity. New Distinguished Contributors are
+selected twice per year by those listed below.
+
+Distinguished Contributors is not meant as a means of conveying
+responsibilities. Distinguished Contributors who are also active maintainers of
+the PyGMT project and have responsibilities detailed in the
+{doc}`maintenance` have 'Maintainer' listed below their names.
+
+```{raw} html
+
+    <div class="team-row">
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/3974108?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Dongdong Tian</h6>
+            <h7 class="team-card-text">Maintainer</h7>
+            <h7 class="team-card-text"><a href="https://github.com/seisman" >@seisman</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/50591376?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Jiayuan Yao</h6>
+            <h7 class="team-card-text"><a href="https://github.com/core-man" >@core-man</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/290082?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Leonardo Uieda</h6>
+            <h7 class="team-card-text">Founder</h7>
+            <h7 class="team-card-text"><a href="https://github.com/leouieda" >@leouieda</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/38269494?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Liam Toney</h6>
+            <h7 class="team-card-text"><a href="https://github.com/liamtoney" >@liamtoney</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/14077947?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Meghan Jones</h6>
+            <h7 class="team-card-text">Maintainer</h7>
+            <h7class="team-card-text"><a href="https://github.com/meghanrjones" >@meghanrjones</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/23025878?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Michael Grund</h6>
+            <h7 class="team-card-text">Maintainer</h7>
+            <h7class="team-card-text"><a href="https://github.com/michaelgrund" >@michaelgrund</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/26473567?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Paul Wessel</h6>
+            <h7 class="team-card-text">Founder</h7>
+            <h7class="team-card-text"><a href="https://github.com/PaulWessel" >@PaulWessel</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/23487320?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Wei Ji Leong</h6>
+            <h7 class="team-card-text">Maintainer</h7>
+            <h7class="team-card-text"><a href="https://github.com/weiji14" >@weiji14</a></h7>
+        </div>
+    </div>
+    <div class="team-card">
+        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/29518865?v=4" alt="Card image">
+        <div class="team-card-body">
+            <h6 class="team-card-text">Will Schlitzer</h6>
+            <h7 class="team-card-text">Maintainer</h7>
+            <p class="team-card-text"><a href="https://github.com/willschlitzer" >@willschlitzer</a></h7>
+        </div>
+    </div>
+    </div>
+```
diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py
index e931b7be..4756fad5 100644
--- a/pygmt/src/grdtrack.py
+++ b/pygmt/src/grdtrack.py
@@ -7,7 +7,6 @@ from pygmt.exceptions import GMTInvalidInput
 from pygmt.helpers import (
     GMTTempFile,
     build_arg_string,
-    data_kind,
     fmt_docstring,
     kwargs_to_strings,
     use_alias,
@@ -248,7 +247,7 @@ def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs):
         - None if ``outfile`` is set (track output will be stored in file set
           by ``outfile``)
     """
-    if data_kind(points) == "matrix" and newcolname is None:
+    if hasattr(points, "columns") and newcolname is None:
         raise GMTInvalidInput("Please pass in a str to 'newcolname'")
 
     with GMTTempFile(suffix=".csv") as tmpfile:
diff --git a/pygmt/src/x2sys_cross.py b/pygmt/src/x2sys_cross.py
index dd9fef6c..5fab91b1 100644
--- a/pygmt/src/x2sys_cross.py
+++ b/pygmt/src/x2sys_cross.py
@@ -47,6 +47,7 @@ def tempfile_from_dftrack(track, suffix):
             path_or_buf=tmpfilename,
             sep="\t",
             index=False,
+            na_rep="NaN",  # write a NaN value explicitly instead of a blank string
             date_format="%Y-%m-%dT%H:%M:%S.%fZ",
         )
         yield tmpfilename


Test patch:
diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py
index 43d766ca..50fafba2 100644
--- a/pygmt/tests/test_x2sys_cross.py
+++ b/pygmt/tests/test_x2sys_cross.py
@@ -75,7 +75,7 @@ def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home):
 
 def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks):
     """
-    Run x2sys_cross by passing in one dataframe, and output external crossovers
+    Run x2sys_cross by passing in one dataframe, and output internal crossovers
     to a pandas.DataFrame.
     """
     with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir:
@@ -129,6 +129,29 @@ def test_x2sys_cross_input_two_dataframes(mock_x2sys_home):
         assert output.dtypes["t_2"].type == np.datetime64
 
 
+def test_x2sys_cross_input_dataframe_with_nan(mock_x2sys_home, tracks):
+    """
+    Run x2sys_cross by passing in one dataframe with NaN values, and output
+    internal crossovers to a pandas.DataFrame.
+    """
+    with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir:
+        tag = os.path.basename(tmpdir)
+        x2sys_init(
+            tag=tag, fmtfile="xyz", suffix="xyzt", units=["de", "se"], force=True
+        )
+
+        tracks[0].loc[tracks[0]["z"] < -15, "z"] = np.nan  # set some values to NaN
+        output = x2sys_cross(tracks=tracks, tag=tag, coe="i")
+
+        assert isinstance(output, pd.DataFrame)
+        assert output.shape == (3, 12)
+        columns = list(output.columns)
+        assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"]
+        assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"]
+        assert output.dtypes["i_1"].type == np.object_
+        assert output.dtypes["i_2"].type == np.object_
+
+
 def test_x2sys_cross_input_two_filenames(mock_x2sys_home):
     """
     Run x2sys_cross by passing in two filenames, and output external crossovers