Repository: bids-standard/pybids

Issue:
First line omitted when using get_df() method of BIDSDataFile object
Hi,

The method `get_df() `of the BIDSDataFile object omits the first line of the data file. This issue arises when calling the function `read_csv()` of Pandas, as header is set to 0 if no names are passed:

```
self.data = pd.read_csv(self.path, sep='\t', na_values='n/a',
                                dtype=dtype)
```

This behaviour can be problematic if one is working with the physiological recording data, since they  don't have a header line and therefore the first observation would be always lost. 

This could be solved by modifying the piece of code above as follows:

```
if self.entities['suffix'] == 'physio' or 'stim':
    self.data = pd.read_csv(self.path, sep='\t', na_values='n/a', 
                            header=None, dtype=dtype)
else:
    self.data = pd.read_csv(self.path, sep='\t', na_values='n/a',
                            dtype=dtype)
```

Does this modification make sense?



Discussion:
Hi @jrasero, sorry, for some reason I missed this a couple of weeks ago. Thanks for catching this! A PR would be great if you want to open one. Haven't tested, but you could probably simplify this to something like:

```python
header = None if self.entities['suffix'] in {'physio', 'stim'} else 'infer'
self.data = pd.read_csv(self.path, sep='\t', na_values='n/a', header=header, dtype=dtype)
```
If you don't have time to open a PR, no worries—I'll get to it when I can. (We should probably also add tests to make sure this kind of thing doesn't happen again.)

Resolving patch:
diff --git a/bids/layout/models.py b/bids/layout/models.py
--- a/bids/layout/models.py
+++ b/bids/layout/models.py
@@ -309,7 +309,7 @@ class BIDSDataFile(BIDSFile):
     }
 
     def get_df(self, include_timing=True, adjust_onset=False,
-               enforce_dtypes=True):
+               enforce_dtypes=True, **pd_args):
         """Return the contents of a tsv file as a pandas DataFrame.
 
         Parameters
@@ -326,6 +326,8 @@ def get_df(self, include_timing=True, adjust_onset=False,
             If True, enforces the data types defined in
             the BIDS spec on core columns (e.g., subject_id and session_id
             must be represented as strings).
+        pd_args : dict
+            Optional keyword arguments to pass onto pd.read_csv().
 
         Returns
         -------
@@ -347,8 +349,10 @@ def get_df(self, include_timing=True, adjust_onset=False,
         # TODO: memoize this for efficiency. (Note: caching is insufficient,
         # because the dtype enforcement will break if we ignore the value of
         # enforce_dtypes).
+        suffix = self.entities['suffix']
+        header = None if suffix in {'physio', 'stim'} else 'infer'
         self.data = pd.read_csv(self.path, sep='\t', na_values='n/a',
-                                dtype=dtype)
+                                dtype=dtype, header=header, **pd_args)
 
         data = self.data.copy()
 


Test patch:
diff --git a/bids/layout/tests/test_models.py b/bids/layout/tests/test_models.py
--- a/bids/layout/tests/test_models.py
+++ b/bids/layout/tests/test_models.py
@@ -176,7 +176,7 @@ def test_bidsfile_get_df_from_tsv_gz(layout_synthetic):
     df1 = bf.get_df()
     df2 = bf.get_df(include_timing=True)
     assert df1.equals(df2)
-    assert df1.shape == (1599, 3)
+    assert df1.shape == (1600, 3)
     assert set(df1.columns) == {'onset', 'respiratory', 'cardiac'}
     assert df1.iloc[0, 0] == 0.
     assert df1.iloc[1, 0] - df1.iloc[0, 0] == 0.1
@@ -190,6 +190,7 @@ def test_bidsfile_get_df_from_tsv_gz(layout_synthetic):
 def test_bidsdatafile_enforces_dtype(layout_synthetic):
     bf = layout_synthetic.get(suffix='participants', extension='tsv')[0]
     df = bf.get_df(enforce_dtypes=False)
+    assert df.shape[0] == 5
     assert df.loc[:, 'subject_id'].dtype == int
     assert df.loc[:, 'subject_id'][0] == 1
     df = bf.get_df(enforce_dtypes=True)