We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Lag transformation
Thinking of implementing Lag, since it's such a common transformation in practice.

----

Current docs:

> ### Lag(Input, Output=None):
> [TODO]
> 
> Arguments:
> * Input(string, mandatory): The name of the variable to operate on.
> * Output (string, optional): the optional list of column names to write out to. By default, computation is done in-place (i.e., input columnise overwritten).

----

It's a bit under-specified, so while fleshing it out (and implementing) we have opportunities to think about what would make it most useful. Questions:

1) Should we permit a lag != 1? If so, is definitially `Lag(Lag(x, 1), 1) = Lag(x, 2)`?
2) Should we permit negative lags?
3) Should we allow taking the difference in the same step? So we get `Lag(x, diff=True) = Lag(x, diff=False) - x`? (or `x - Lag(x, diff=False)`)?
4) Should we default to overwriting columns or generating new columns from the original name? BEP 012 [would suggest](https://github.com/bids-standard/bids-specification/pull/519/files#diff-093ab536060c8ee949aa452200ec2676377a2fcd1a8674f91c8e6a1cfd42281fR127) calling `Lag(x) = x_shift_back`, though `x_lag` might be clearer.

Any other thoughts?

cc @adelavega 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bids/modeling/transformations/compute.py`
Content:
```
1 """
2 Transformations that primarily involve numerical computation on variables.
3 """
4 import math
5 import numpy as np
6 import pandas as pd
7 from bids.utils import listify
8 from .base import Transformation
9 from bids.modeling import hrf
10 from bids.variables import SparseRunVariable,  DenseRunVariable
11 
12 
13 def _fractional_gcd(vals, res=0.001):
14     from functools import reduce
15     from math import gcd
16     return reduce(gcd, (int(np.round(val / res)) for val in vals)) * res
17 
18 
19 class Convolve(Transformation):
20     """Convolve the input variable with an HRF.
21 
22     Parameters
23     ----------
24     var : Variable
25         The variable to convolve.
26     model : str
27         The name of the HRF model to apply. Must be one of 'spm',
28         'glover', or 'fir'.
29     derivative : bool
30         Whether or not to include the temporal derivative.
31     dispersion : bool
32         Whether or not to include the dispersion derivative.
33     fir_delays : iterable
34         A list or iterable of delays to use if model is
35         'fir' (ignored otherwise). Spacing between delays must be fixed.
36 
37     Notes
38     -----
39     Uses the HRF convolution functions implemented in nistats.
40     """
41 
42     _input_type = 'variable'
43     _return_type = 'variable'
44 
45     def _transform(self, var, model='spm', derivative=False, dispersion=False,
46                    fir_delays=None):
47 
48         model = model.lower()
49 
50         df = var.to_df(entities=False)
51 
52         if isinstance(var, SparseRunVariable):
53             sampling_rate = self.collection.sampling_rate
54             dur = var.get_duration()
55             resample_frames = np.linspace(
56                 0, dur, int(math.ceil(dur * sampling_rate)), endpoint=False)
57             safety = 2  # Double frequency to resolve events
58         else:
59             resample_frames = df['onset'].values
60             sampling_rate = var.sampling_rate
61             safety = 1  # Maximum signal resolution is already 0.5 * SR
62 
63         vals = df[['onset', 'duration', 'amplitude']].values.T
64 
65         if model in ['spm', 'glover']:
66             if derivative:
67                 model += ' + derivative'
68             if dispersion:
69                 model += ' + dispersion'
70         elif model != 'fir':
71             raise ValueError("Model must be one of 'spm', 'glover', or 'fir'.")
72 
73         # Sampling at >100Hz will never be useful, but can be wildly expensive
74         max_freq, min_interval = 100, 0.01
75         # Sampling at <1Hz can degrade signals
76         min_freq, max_interval = 1, 1
77 
78         # Given the sampling rate, determine an oversampling factor to ensure that
79         # events can be modeled with reasonable precision
80         unique_onsets = np.unique(df.onset)
81         unique_durations = np.unique(df.duration)
82         # Align existing data ticks with, event onsets and offsets, up to ms resolution
83         # Note that GCD ignores zeros, so 0 onsets and impulse responses (0 durations) do
84         # not harm this.
85         required_resolution = _fractional_gcd(
86             np.concatenate((unique_onsets, unique_durations)),
87             res=min_interval)
88         # Bound the effective sampling rate between min_freq and max_freq
89         effective_sr = max(min_freq, min(safety / required_resolution, max_freq))
90         convolved = hrf.compute_regressor(
91             vals, model, resample_frames, fir_delays=fir_delays, min_onset=0,
92             oversampling=np.ceil(effective_sr / sampling_rate)
93             )
94 
95         return DenseRunVariable(
96             name=var.name, values=convolved[0], run_info=var.run_info,
97             source=var.source, sampling_rate=sampling_rate)
98 
99 
100 class Demean(Transformation):
101 
102     def _transform(self, data):
103         return data - data.mean()
104 
105 
106 class Orthogonalize(Transformation):
107 
108     _variables_used = ('variables', 'other')
109     _densify = ('variables', 'other')
110     _aligned_required = 'force_dense'
111     _aligned_variables = ('other')
112 
113     def _transform(self, var, other):
114 
115         other = listify(other)
116 
117         # Set up X matrix and slice into it based on target variable indices
118         X = np.array([self._variables[c].values.values.squeeze()
119                       for c in other]).T
120         X = X[var.index, :]
121         assert len(X) == len(var)
122         y = var.values
123         _aX = np.c_[np.ones(len(y)), X]
124         coefs, resids, rank, s = np.linalg.lstsq(_aX, y, rcond=None)
125         result = pd.DataFrame(y - X.dot(coefs[1:]), index=var.index)
126         return result
127 
128 
129 class Product(Transformation):
130 
131     _loopable = False
132     _groupable = False
133     _aligned_required = True
134     _output_required = True
135 
136     def _transform(self, data):
137         data = pd.concat(data, axis=1, sort=True)
138         return data.product(1)
139 
140 
141 class Scale(Transformation):
142     """Scale a variable.
143 
144     Parameters
145     ----------
146     data : :obj:`pandas.Series` or :obj:`pandas.DataFrame`
147         The variables to scale.
148     demean : bool
149         If True, demean each column.
150     rescale : bool
151         If True, divide variables by their standard deviation.
152     replace_na : str
153         Whether/when to replace missing values with 0. If
154         None, no replacement is performed. If 'before', missing values are
155         replaced with 0's before scaling. If 'after', missing values are
156         replaced with 0 after scaling.
157 
158     Notes
159     -----
160     If a constant column is passed in, and replace_na is None or 'before', an
161     exception will be raised.
162     """
163 
164     def _transform(self, data, demean=True, rescale=True, replace_na=None):
165         if data.nunique() == 1 and replace_na in {None, 'before'}:
166             val = data.unique()[0]
167             raise ValueError("Cannot scale a column with constant value ({})! "
168                              "If you want a constant column of 0's returned, "
169                              "set replace_na to 'after'.".format(val))
170         if replace_na == 'before':
171             data = data.fillna(0.)
172         if demean:
173             data -= data.mean()
174         if rescale:
175             data /= data.std()
176         if replace_na == 'after':
177             data = data.fillna(0.)
178         return data
179 
180 
181 class Sum(Transformation):
182 
183     _loopable = False
184     _groupable = False
185     _aligned_required = True
186     _output_required = True
187 
188     def _transform(self, data, weights=None):
189         data = pd.concat(data, axis=1, sort=True)
190         if weights is None:
191             weights = np.ones(data.shape[1])
192         else:
193             weights = np.array(weights)
194             if len(weights.ravel()) != data.shape[1]:
195                 raise ValueError("If weights are passed to sum(), the number "
196                                  "of elements must equal number of variables"
197                                  " being summed.")
198         return (data * weights).sum(axis=1)
199 
200 
201 class Threshold(Transformation):
202     """Threshold and/or binarize a variable.
203 
204     Parameters
205     ----------
206     data :obj:`pandas.Series` or :obj:`pandas.DataFrame`
207         The pandas structure to threshold.
208     threshold : float
209         The value to binarize around (values above will
210         be assigned 1, values below will be assigned 0).
211     binarize : bool
212         If True, binarizes all non-zero values (i.e., every
213         non-zero value will be set to 1).
214     above : bool
215         Specifies which values to retain with respect to the
216         cut-off. If True, all value above the threshold will be kept; if
217         False, all values below the threshold will be kept. Defaults to
218         True.
219     signed : bool
220         Specifies whether to treat the threshold as signed
221         (default) or unsigned. For example, when passing above=True and
222         threshold=3, if signed=True, all and only values above +3 would be
223         retained. If signed=False, all absolute values > 3 would be retained
224         (i.e.,values in  the range -3 < X < 3 would be set to 0).
225     """
226 
227     _groupable = False
228 
229     def _transform(self, data, threshold=0., binarize=False, above=True,
230                    signed=True):
231         if not signed:
232             threshold = np.abs(threshold)
233             data = data.abs()
234         keep = data >= threshold if above else data <= threshold
235         data[~keep] = 0
236         if binarize:
237             data[keep] = 1
238         return data
239 
240 
241 class And_(Transformation):
242     """Logical AND on two or more variables.
243 
244     Parameters
245     ----------
246     dfs : list of :obj:`pandas.DataFrame`
247         variables to enter into the conjunction.
248     """
249 
250     _loopable = False
251     _groupable = False
252     _output_required = True
253     _aligned_required = True
254 
255     def _transform(self, dfs):
256         df = pd.concat(dfs, axis=1, sort=True)
257         return df.all(axis=1).astype(int)
258 
259 
260 class Not(Transformation):
261     """Logical negation of a variable.
262 
263     Parameters
264     ----------
265     var : :obj:`pandas.Series`
266         Variable to negate. Must be convertible to bool.
267     """
268 
269     _loopable = True
270     _groupable = False
271 
272     def _transform(self, var):
273         return ~var.astype(bool)
274 
275 
276 class Or_(Transformation):
277     """Logical OR (inclusive) on two or more variables.
278 
279     Parameters
280     ----------
281     dfs : list of :obj:`pandas.DataFrame`
282         variables to enter into the disjunction.
283     """
284 
285     _loopable = False
286     _groupable = False
287     _output_required = True
288     _aligned_required = True
289 
290     def _transform(self, dfs):
291         df = pd.concat(dfs, axis=1, sort=True)
292         return df.any(axis=1).astype(int)
293
```

Path: `bids/modeling/transformations/__init__.py`
Content:
```
1 from .compute import (Sum, Product, Scale, Orthogonalize, Threshold, And_, Or_,
2                       Not, Demean, Convolve)
3 from .munge import (Split, Rename, Assign, Copy, Factor, Filter, Select,
4                     Delete, DropNA, Replace, ToDense, Group, Resample)
5 from .base import TransformerManager
6 
7 And = And_
8 Or = Or_
9 
10 __all__ = [
11     'And',
12     'And_',
13     'Assign',
14     'Convolve',
15     'Copy',
16     'Demean',
17     'Delete',
18     'DropNA',
19     'Factor',
20     'Filter',
21     'Group',
22     'Not',
23     'Or',
24     'Or_',
25     'Orthogonalize',
26     'Product',
27     'Rename',
28     'Replace',
29     'Resample',
30     'Scale',
31     'Select',
32     'Split',
33     'Sum',
34     'Threshold',
35     'ToDense',
36     'TransformerManager'
37 ]
38
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bids/modeling/transformations/__init__.py b/bids/modeling/transformations/__init__.py
--- a/bids/modeling/transformations/__init__.py
+++ b/bids/modeling/transformations/__init__.py
@@ -1,5 +1,5 @@
 from .compute import (Sum, Product, Scale, Orthogonalize, Threshold, And_, Or_,
-                      Not, Demean, Convolve)
+                      Not, Demean, Convolve, Lag)
 from .munge import (Split, Rename, Assign, Copy, Factor, Filter, Select,
                     Delete, DropNA, Replace, ToDense, Group, Resample)
 from .base import TransformerManager
@@ -19,6 +19,7 @@
     'Factor',
     'Filter',
     'Group',
+    'Lag',
     'Not',
     'Or',
     'Or_',
diff --git a/bids/modeling/transformations/compute.py b/bids/modeling/transformations/compute.py
--- a/bids/modeling/transformations/compute.py
+++ b/bids/modeling/transformations/compute.py
@@ -4,6 +4,7 @@
 import math
 import numpy as np
 import pandas as pd
+from scipy import ndimage as ndi
 from bids.utils import listify
 from .base import Transformation
 from bids.modeling import hrf
@@ -290,3 +291,68 @@
     def _transform(self, dfs):
         df = pd.concat(dfs, axis=1, sort=True)
         return df.any(axis=1).astype(int)
+
+
+class Lag(Transformation):
+    """Lag variable
+
+    Returns a variable that is lagged by a specified number of time points.
+    Spline interpolation of the requested ``order`` is used for non-integer
+    shifts.
+    Points outside the input are filled according to the given ``mode``.
+    Negative shifts move values toward the beginning of the sequence.
+    If ``difference`` is ``True``, the backward difference is calculated for
+    positive shifts, and the forward difference is calculated for negative
+    shifts.
+
+    Additional ``mode``s may be defined if there is need for them.
+    The ``scipy.ndimage.shift`` method provides the reference implementation
+    for all current modes. "Constant" is equivalent to the shift parameter
+    "cval".
+
+
+    Parameters
+    ----------
+    var : :obj:`numpy.ndarray`
+        variable to lag
+    shift : float, optional
+        number of places to shift the values (default: 1)
+    order : int, optional
+        order of spline interpolation, from 0-5 (default: 3)
+    mode : string
+        the `mode` parameter determines how the input array is extended
+        beyond its boundaries. Default is 'nearest'.
+        The following values are accepted:
+
+        "nearest" (a a a a | a b c d | d d d d)
+            The input is extended by replicating the boundary values
+        "reflect" (d c b a | a b c d | d c b a)
+            The input is extended by reflecting the array over the edge
+        "constant" (k k k k | a b c d | k k k k)
+            The input is extended by filling all values beyond the edge
+            with the same constant value, defined by the ``constant`` parameter
+    constant : float, optional
+        value to fill past edges of input if ``mode`` is ``"constant"``.
+        (default: 0)
+    difference : boolean, optional
+        Calculate the backward (if shift is positive) or forward (if shift is
+        negative) difference.
+        For the forward difference dx of an array x, dx[i] = x[i+1] - x[i].
+        For the backward difference dx of an array x, dx[i] = x[i] - x[i-1].
+        (default: ``False``)
+    """
+
+    _input_type = 'numpy'
+    _return_type = 'numpy'
+
+    def _transform(self, var, shift=1, order=3, mode="nearest",
+                   constant=0.0, difference=False):
+        var = var.flatten()
+        shifted = ndi.shift(var, shift=shift, order=order, mode=mode,
+                            cval=constant)
+        if not difference:
+            return shifted
+        elif shift >= 0:
+            return var - shifted
+        else:
+            return shifted - var

{"golden_diff": "diff --git a/bids/modeling/transformations/__init__.py b/bids/modeling/transformations/__init__.py\n--- a/bids/modeling/transformations/__init__.py\n+++ b/bids/modeling/transformations/__init__.py\n@@ -1,5 +1,5 @@\n from .compute import (Sum, Product, Scale, Orthogonalize, Threshold, And_, Or_,\n-                      Not, Demean, Convolve)\n+                      Not, Demean, Convolve, Lag)\n from .munge import (Split, Rename, Assign, Copy, Factor, Filter, Select,\n                     Delete, DropNA, Replace, ToDense, Group, Resample)\n from .base import TransformerManager\n@@ -19,6 +19,7 @@\n     'Factor',\n     'Filter',\n     'Group',\n+    'Lag',\n     'Not',\n     'Or',\n     'Or_',\ndiff --git a/bids/modeling/transformations/compute.py b/bids/modeling/transformations/compute.py\n--- a/bids/modeling/transformations/compute.py\n+++ b/bids/modeling/transformations/compute.py\n@@ -4,6 +4,7 @@\n import math\n import numpy as np\n import pandas as pd\n+from scipy import ndimage as ndi\n from bids.utils import listify\n from .base import Transformation\n from bids.modeling import hrf\n@@ -290,3 +291,68 @@\n     def _transform(self, dfs):\n         df = pd.concat(dfs, axis=1, sort=True)\n         return df.any(axis=1).astype(int)\n+\n+\n+class Lag(Transformation):\n+    \"\"\"Lag variable\n+\n+    Returns a variable that is lagged by a specified number of time points.\n+    Spline interpolation of the requested ``order`` is used for non-integer\n+    shifts.\n+    Points outside the input are filled according to the given ``mode``.\n+    Negative shifts move values toward the beginning of the sequence.\n+    If ``difference`` is ``True``, the backward difference is calculated for\n+    positive shifts, and the forward difference is calculated for negative\n+    shifts.\n+\n+    Additional ``mode``s may be defined if there is need for them.\n+    The ``scipy.ndimage.shift`` method provides the reference implementation\n+    for all current modes. \"Constant\" is equivalent to the shift parameter\n+    \"cval\".\n+\n+\n+    Parameters\n+    ----------\n+    var : :obj:`numpy.ndarray`\n+        variable to lag\n+    shift : float, optional\n+        number of places to shift the values (default: 1)\n+    order : int, optional\n+        order of spline interpolation, from 0-5 (default: 3)\n+    mode : string\n+        the `mode` parameter determines how the input array is extended\n+        beyond its boundaries. Default is 'nearest'.\n+        The following values are accepted:\n+\n+        \"nearest\" (a a a a | a b c d | d d d d)\n+            The input is extended by replicating the boundary values\n+        \"reflect\" (d c b a | a b c d | d c b a)\n+            The input is extended by reflecting the array over the edge\n+        \"constant\" (k k k k | a b c d | k k k k)\n+            The input is extended by filling all values beyond the edge\n+            with the same constant value, defined by the ``constant`` parameter\n+    constant : float, optional\n+        value to fill past edges of input if ``mode`` is ``\"constant\"``.\n+        (default: 0)\n+    difference : boolean, optional\n+        Calculate the backward (if shift is positive) or forward (if shift is\n+        negative) difference.\n+        For the forward difference dx of an array x, dx[i] = x[i+1] - x[i].\n+        For the backward difference dx of an array x, dx[i] = x[i] - x[i-1].\n+        (default: ``False``)\n+    \"\"\"\n+\n+    _input_type = 'numpy'\n+    _return_type = 'numpy'\n+\n+    def _transform(self, var, shift=1, order=3, mode=\"nearest\",\n+                   constant=0.0, difference=False):\n+        var = var.flatten()\n+        shifted = ndi.shift(var, shift=shift, order=order, mode=mode,\n+                            cval=constant)\n+        if not difference:\n+            return shifted\n+        elif shift >= 0:\n+            return var - shifted\n+        else:\n+            return shifted - var\n", "issue": "Lag transformation\nThinking of implementing Lag, since it's such a common transformation in practice.\r\n\r\n----\r\n\r\nCurrent docs:\r\n\r\n> ### Lag(Input, Output=None):\r\n> [TODO]\r\n> \r\n> Arguments:\r\n> * Input(string, mandatory): The name of the variable to operate on.\r\n> * Output (string, optional): the optional list of column names to write out to. By default, computation is done in-place (i.e., input columnise overwritten).\r\n\r\n----\r\n\r\nIt's a bit under-specified, so while fleshing it out (and implementing) we have opportunities to think about what would make it most useful. Questions:\r\n\r\n1) Should we permit a lag != 1? If so, is definitially `Lag(Lag(x, 1), 1) = Lag(x, 2)`?\r\n2) Should we permit negative lags?\r\n3) Should we allow taking the difference in the same step? So we get `Lag(x, diff=True) = Lag(x, diff=False) - x`? (or `x - Lag(x, diff=False)`)?\r\n4) Should we default to overwriting columns or generating new columns from the original name? BEP 012 [would suggest](https://github.com/bids-standard/bids-specification/pull/519/files#diff-093ab536060c8ee949aa452200ec2676377a2fcd1a8674f91c8e6a1cfd42281fR127) calling `Lag(x) = x_shift_back`, though `x_lag` might be clearer.\r\n\r\nAny other thoughts?\r\n\r\ncc @adelavega \n", "before_files": [{"content": "\"\"\"\nTransformations that primarily involve numerical computation on variables.\n\"\"\"\nimport math\nimport numpy as np\nimport pandas as pd\nfrom bids.utils import listify\nfrom .base import Transformation\nfrom bids.modeling import hrf\nfrom bids.variables import SparseRunVariable,  DenseRunVariable\n\n\ndef _fractional_gcd(vals, res=0.001):\n    from functools import reduce\n    from math import gcd\n    return reduce(gcd, (int(np.round(val / res)) for val in vals)) * res\n\n\nclass Convolve(Transformation):\n    \"\"\"Convolve the input variable with an HRF.\n\n    Parameters\n    ----------\n    var : Variable\n        The variable to convolve.\n    model : str\n        The name of the HRF model to apply. Must be one of 'spm',\n        'glover', or 'fir'.\n    derivative : bool\n        Whether or not to include the temporal derivative.\n    dispersion : bool\n        Whether or not to include the dispersion derivative.\n    fir_delays : iterable\n        A list or iterable of delays to use if model is\n        'fir' (ignored otherwise). Spacing between delays must be fixed.\n\n    Notes\n    -----\n    Uses the HRF convolution functions implemented in nistats.\n    \"\"\"\n\n    _input_type = 'variable'\n    _return_type = 'variable'\n\n    def _transform(self, var, model='spm', derivative=False, dispersion=False,\n                   fir_delays=None):\n\n        model = model.lower()\n\n        df = var.to_df(entities=False)\n\n        if isinstance(var, SparseRunVariable):\n            sampling_rate = self.collection.sampling_rate\n            dur = var.get_duration()\n            resample_frames = np.linspace(\n                0, dur, int(math.ceil(dur * sampling_rate)), endpoint=False)\n            safety = 2  # Double frequency to resolve events\n        else:\n            resample_frames = df['onset'].values\n            sampling_rate = var.sampling_rate\n            safety = 1  # Maximum signal resolution is already 0.5 * SR\n\n        vals = df[['onset', 'duration', 'amplitude']].values.T\n\n        if model in ['spm', 'glover']:\n            if derivative:\n                model += ' + derivative'\n            if dispersion:\n                model += ' + dispersion'\n        elif model != 'fir':\n            raise ValueError(\"Model must be one of 'spm', 'glover', or 'fir'.\")\n\n        # Sampling at >100Hz will never be useful, but can be wildly expensive\n        max_freq, min_interval = 100, 0.01\n        # Sampling at <1Hz can degrade signals\n        min_freq, max_interval = 1, 1\n\n        # Given the sampling rate, determine an oversampling factor to ensure that\n        # events can be modeled with reasonable precision\n        unique_onsets = np.unique(df.onset)\n        unique_durations = np.unique(df.duration)\n        # Align existing data ticks with, event onsets and offsets, up to ms resolution\n        # Note that GCD ignores zeros, so 0 onsets and impulse responses (0 durations) do\n        # not harm this.\n        required_resolution = _fractional_gcd(\n            np.concatenate((unique_onsets, unique_durations)),\n            res=min_interval)\n        # Bound the effective sampling rate between min_freq and max_freq\n        effective_sr = max(min_freq, min(safety / required_resolution, max_freq))\n        convolved = hrf.compute_regressor(\n            vals, model, resample_frames, fir_delays=fir_delays, min_onset=0,\n            oversampling=np.ceil(effective_sr / sampling_rate)\n            )\n\n        return DenseRunVariable(\n            name=var.name, values=convolved[0], run_info=var.run_info,\n            source=var.source, sampling_rate=sampling_rate)\n\n\nclass Demean(Transformation):\n\n    def _transform(self, data):\n        return data - data.mean()\n\n\nclass Orthogonalize(Transformation):\n\n    _variables_used = ('variables', 'other')\n    _densify = ('variables', 'other')\n    _aligned_required = 'force_dense'\n    _aligned_variables = ('other')\n\n    def _transform(self, var, other):\n\n        other = listify(other)\n\n        # Set up X matrix and slice into it based on target variable indices\n        X = np.array([self._variables[c].values.values.squeeze()\n                      for c in other]).T\n        X = X[var.index, :]\n        assert len(X) == len(var)\n        y = var.values\n        _aX = np.c_[np.ones(len(y)), X]\n        coefs, resids, rank, s = np.linalg.lstsq(_aX, y, rcond=None)\n        result = pd.DataFrame(y - X.dot(coefs[1:]), index=var.index)\n        return result\n\n\nclass Product(Transformation):\n\n    _loopable = False\n    _groupable = False\n    _aligned_required = True\n    _output_required = True\n\n    def _transform(self, data):\n        data = pd.concat(data, axis=1, sort=True)\n        return data.product(1)\n\n\nclass Scale(Transformation):\n    \"\"\"Scale a variable.\n\n    Parameters\n    ----------\n    data : :obj:`pandas.Series` or :obj:`pandas.DataFrame`\n        The variables to scale.\n    demean : bool\n        If True, demean each column.\n    rescale : bool\n        If True, divide variables by their standard deviation.\n    replace_na : str\n        Whether/when to replace missing values with 0. If\n        None, no replacement is performed. If 'before', missing values are\n        replaced with 0's before scaling. If 'after', missing values are\n        replaced with 0 after scaling.\n\n    Notes\n    -----\n    If a constant column is passed in, and replace_na is None or 'before', an\n    exception will be raised.\n    \"\"\"\n\n    def _transform(self, data, demean=True, rescale=True, replace_na=None):\n        if data.nunique() == 1 and replace_na in {None, 'before'}:\n            val = data.unique()[0]\n            raise ValueError(\"Cannot scale a column with constant value ({})! \"\n                             \"If you want a constant column of 0's returned, \"\n                             \"set replace_na to 'after'.\".format(val))\n        if replace_na == 'before':\n            data = data.fillna(0.)\n        if demean:\n            data -= data.mean()\n        if rescale:\n            data /= data.std()\n        if replace_na == 'after':\n            data = data.fillna(0.)\n        return data\n\n\nclass Sum(Transformation):\n\n    _loopable = False\n    _groupable = False\n    _aligned_required = True\n    _output_required = True\n\n    def _transform(self, data, weights=None):\n        data = pd.concat(data, axis=1, sort=True)\n        if weights is None:\n            weights = np.ones(data.shape[1])\n        else:\n            weights = np.array(weights)\n            if len(weights.ravel()) != data.shape[1]:\n                raise ValueError(\"If weights are passed to sum(), the number \"\n                                 \"of elements must equal number of variables\"\n                                 \" being summed.\")\n        return (data * weights).sum(axis=1)\n\n\nclass Threshold(Transformation):\n    \"\"\"Threshold and/or binarize a variable.\n\n    Parameters\n    ----------\n    data :obj:`pandas.Series` or :obj:`pandas.DataFrame`\n        The pandas structure to threshold.\n    threshold : float\n        The value to binarize around (values above will\n        be assigned 1, values below will be assigned 0).\n    binarize : bool\n        If True, binarizes all non-zero values (i.e., every\n        non-zero value will be set to 1).\n    above : bool\n        Specifies which values to retain with respect to the\n        cut-off. If True, all value above the threshold will be kept; if\n        False, all values below the threshold will be kept. Defaults to\n        True.\n    signed : bool\n        Specifies whether to treat the threshold as signed\n        (default) or unsigned. For example, when passing above=True and\n        threshold=3, if signed=True, all and only values above +3 would be\n        retained. If signed=False, all absolute values > 3 would be retained\n        (i.e.,values in  the range -3 < X < 3 would be set to 0).\n    \"\"\"\n\n    _groupable = False\n\n    def _transform(self, data, threshold=0., binarize=False, above=True,\n                   signed=True):\n        if not signed:\n            threshold = np.abs(threshold)\n            data = data.abs()\n        keep = data >= threshold if above else data <= threshold\n        data[~keep] = 0\n        if binarize:\n            data[keep] = 1\n        return data\n\n\nclass And_(Transformation):\n    \"\"\"Logical AND on two or more variables.\n\n    Parameters\n    ----------\n    dfs : list of :obj:`pandas.DataFrame`\n        variables to enter into the conjunction.\n    \"\"\"\n\n    _loopable = False\n    _groupable = False\n    _output_required = True\n    _aligned_required = True\n\n    def _transform(self, dfs):\n        df = pd.concat(dfs, axis=1, sort=True)\n        return df.all(axis=1).astype(int)\n\n\nclass Not(Transformation):\n    \"\"\"Logical negation of a variable.\n\n    Parameters\n    ----------\n    var : :obj:`pandas.Series`\n        Variable to negate. Must be convertible to bool.\n    \"\"\"\n\n    _loopable = True\n    _groupable = False\n\n    def _transform(self, var):\n        return ~var.astype(bool)\n\n\nclass Or_(Transformation):\n    \"\"\"Logical OR (inclusive) on two or more variables.\n\n    Parameters\n    ----------\n    dfs : list of :obj:`pandas.DataFrame`\n        variables to enter into the disjunction.\n    \"\"\"\n\n    _loopable = False\n    _groupable = False\n    _output_required = True\n    _aligned_required = True\n\n    def _transform(self, dfs):\n        df = pd.concat(dfs, axis=1, sort=True)\n        return df.any(axis=1).astype(int)\n", "path": "bids/modeling/transformations/compute.py"}, {"content": "from .compute import (Sum, Product, Scale, Orthogonalize, Threshold, And_, Or_,\n                      Not, Demean, Convolve)\nfrom .munge import (Split, Rename, Assign, Copy, Factor, Filter, Select,\n                    Delete, DropNA, Replace, ToDense, Group, Resample)\nfrom .base import TransformerManager\n\nAnd = And_\nOr = Or_\n\n__all__ = [\n    'And',\n    'And_',\n    'Assign',\n    'Convolve',\n    'Copy',\n    'Demean',\n    'Delete',\n    'DropNA',\n    'Factor',\n    'Filter',\n    'Group',\n    'Not',\n    'Or',\n    'Or_',\n    'Orthogonalize',\n    'Product',\n    'Rename',\n    'Replace',\n    'Resample',\n    'Scale',\n    'Select',\n    'Split',\n    'Sum',\n    'Threshold',\n    'ToDense',\n    'TransformerManager'\n]\n", "path": "bids/modeling/transformations/__init__.py"}], "after_files": [{"content": "\"\"\"\nTransformations that primarily involve numerical computation on variables.\n\"\"\"\nimport math\nimport numpy as np\nimport pandas as pd\nfrom scipy import ndimage as ndi\nfrom bids.utils import listify\nfrom .base import Transformation\nfrom bids.modeling import hrf\nfrom bids.variables import SparseRunVariable,  DenseRunVariable\n\n\ndef _fractional_gcd(vals, res=0.001):\n    from functools import reduce\n    from math import gcd\n    return reduce(gcd, (int(np.round(val / res)) for val in vals)) * res\n\n\nclass Convolve(Transformation):\n    \"\"\"Convolve the input variable with an HRF.\n\n    Parameters\n    ----------\n    var : Variable\n        The variable to convolve.\n    model : str\n        The name of the HRF model to apply. Must be one of 'spm',\n        'glover', or 'fir'.\n    derivative : bool\n        Whether or not to include the temporal derivative.\n    dispersion : bool\n        Whether or not to include the dispersion derivative.\n    fir_delays : iterable\n        A list or iterable of delays to use if model is\n        'fir' (ignored otherwise). Spacing between delays must be fixed.\n\n    Notes\n    -----\n    Uses the HRF convolution functions implemented in nistats.\n    \"\"\"\n\n    _input_type = 'variable'\n    _return_type = 'variable'\n\n    def _transform(self, var, model='spm', derivative=False, dispersion=False,\n                   fir_delays=None):\n\n        model = model.lower()\n\n        df = var.to_df(entities=False)\n\n        if isinstance(var, SparseRunVariable):\n            sampling_rate = self.collection.sampling_rate\n            dur = var.get_duration()\n            resample_frames = np.linspace(\n                0, dur, int(math.ceil(dur * sampling_rate)), endpoint=False)\n            safety = 2  # Double frequency to resolve events\n        else:\n            resample_frames = df['onset'].values\n            sampling_rate = var.sampling_rate\n            safety = 1  # Maximum signal resolution is already 0.5 * SR\n\n        vals = df[['onset', 'duration', 'amplitude']].values.T\n\n        if model in ['spm', 'glover']:\n            if derivative:\n                model += ' + derivative'\n            if dispersion:\n                model += ' + dispersion'\n        elif model != 'fir':\n            raise ValueError(\"Model must be one of 'spm', 'glover', or 'fir'.\")\n\n        # Sampling at >100Hz will never be useful, but can be wildly expensive\n        max_freq, min_interval = 100, 0.01\n        # Sampling at <1Hz can degrade signals\n        min_freq, max_interval = 1, 1\n\n        # Given the sampling rate, determine an oversampling factor to ensure that\n        # events can be modeled with reasonable precision\n        unique_onsets = np.unique(df.onset)\n        unique_durations = np.unique(df.duration)\n        # Align existing data ticks with, event onsets and offsets, up to ms resolution\n        # Note that GCD ignores zeros, so 0 onsets and impulse responses (0 durations) do\n        # not harm this.\n        required_resolution = _fractional_gcd(\n            np.concatenate((unique_onsets, unique_durations)),\n            res=min_interval)\n        # Bound the effective sampling rate between min_freq and max_freq\n        effective_sr = max(min_freq, min(safety / required_resolution, max_freq))\n        convolved = hrf.compute_regressor(\n            vals, model, resample_frames, fir_delays=fir_delays, min_onset=0,\n            oversampling=np.ceil(effective_sr / sampling_rate)\n            )\n\n        return DenseRunVariable(\n            name=var.name, values=convolved[0], run_info=var.run_info,\n            source=var.source, sampling_rate=sampling_rate)\n\n\nclass Demean(Transformation):\n\n    def _transform(self, data):\n        return data - data.mean()\n\n\nclass Orthogonalize(Transformation):\n\n    _variables_used = ('variables', 'other')\n    _densify = ('variables', 'other')\n    _aligned_required = 'force_dense'\n    _aligned_variables = ('other')\n\n    def _transform(self, var, other):\n\n        other = listify(other)\n\n        # Set up X matrix and slice into it based on target variable indices\n        X = np.array([self._variables[c].values.values.squeeze()\n                      for c in other]).T\n        X = X[var.index, :]\n        assert len(X) == len(var)\n        y = var.values\n        _aX = np.c_[np.ones(len(y)), X]\n        coefs, resids, rank, s = np.linalg.lstsq(_aX, y, rcond=None)\n        result = pd.DataFrame(y - X.dot(coefs[1:]), index=var.index)\n        return result\n\n\nclass Product(Transformation):\n\n    _loopable = False\n    _groupable = False\n    _aligned_required = True\n    _output_required = True\n\n    def _transform(self, data):\n        data = pd.concat(data, axis=1, sort=True)\n        return data.product(1)\n\n\nclass Scale(Transformation):\n    \"\"\"Scale a variable.\n\n    Parameters\n    ----------\n    data : :obj:`pandas.Series` or :obj:`pandas.DataFrame`\n        The variables to scale.\n    demean : bool\n        If True, demean each column.\n    rescale : bool\n        If True, divide variables by their standard deviation.\n    replace_na : str\n        Whether/when to replace missing values with 0. If\n        None, no replacement is performed. If 'before', missing values are\n        replaced with 0's before scaling. If 'after', missing values are\n        replaced with 0 after scaling.\n\n    Notes\n    -----\n    If a constant column is passed in, and replace_na is None or 'before', an\n    exception will be raised.\n    \"\"\"\n\n    def _transform(self, data, demean=True, rescale=True, replace_na=None):\n        if data.nunique() == 1 and replace_na in {None, 'before'}:\n            val = data.unique()[0]\n            raise ValueError(\"Cannot scale a column with constant value ({})! \"\n                             \"If you want a constant column of 0's returned, \"\n                             \"set replace_na to 'after'.\".format(val))\n        if replace_na == 'before':\n            data = data.fillna(0.)\n        if demean:\n            data -= data.mean()\n        if rescale:\n            data /= data.std()\n        if replace_na == 'after':\n            data = data.fillna(0.)\n        return data\n\n\nclass Sum(Transformation):\n\n    _loopable = False\n    _groupable = False\n    _aligned_required = True\n    _output_required = True\n\n    def _transform(self, data, weights=None):\n        data = pd.concat(data, axis=1, sort=True)\n        if weights is None:\n            weights = np.ones(data.shape[1])\n        else:\n            weights = np.array(weights)\n            if len(weights.ravel()) != data.shape[1]:\n                raise ValueError(\"If weights are passed to sum(), the number \"\n                                 \"of elements must equal number of variables\"\n                                 \" being summed.\")\n        return (data * weights).sum(axis=1)\n\n\nclass Threshold(Transformation):\n    \"\"\"Threshold and/or binarize a variable.\n\n    Parameters\n    ----------\n    data :obj:`pandas.Series` or :obj:`pandas.DataFrame`\n        The pandas structure to threshold.\n    threshold : float\n        The value to binarize around (values above will\n        be assigned 1, values below will be assigned 0).\n    binarize : bool\n        If True, binarizes all non-zero values (i.e., every\n        non-zero value will be set to 1).\n    above : bool\n        Specifies which values to retain with respect to the\n        cut-off. If True, all value above the threshold will be kept; if\n        False, all values below the threshold will be kept. Defaults to\n        True.\n    signed : bool\n        Specifies whether to treat the threshold as signed\n        (default) or unsigned. For example, when passing above=True and\n        threshold=3, if signed=True, all and only values above +3 would be\n        retained. If signed=False, all absolute values > 3 would be retained\n        (i.e.,values in  the range -3 < X < 3 would be set to 0).\n    \"\"\"\n\n    _groupable = False\n\n    def _transform(self, data, threshold=0., binarize=False, above=True,\n                   signed=True):\n        if not signed:\n            threshold = np.abs(threshold)\n            data = data.abs()\n        keep = data >= threshold if above else data <= threshold\n        data[~keep] = 0\n        if binarize:\n            data[keep] = 1\n        return data\n\n\nclass And_(Transformation):\n    \"\"\"Logical AND on two or more variables.\n\n    Parameters\n    ----------\n    dfs : list of :obj:`pandas.DataFrame`\n        variables to enter into the conjunction.\n    \"\"\"\n\n    _loopable = False\n    _groupable = False\n    _output_required = True\n    _aligned_required = True\n\n    def _transform(self, dfs):\n        df = pd.concat(dfs, axis=1, sort=True)\n        return df.all(axis=1).astype(int)\n\n\nclass Not(Transformation):\n    \"\"\"Logical negation of a variable.\n\n    Parameters\n    ----------\n    var : :obj:`pandas.Series`\n        Variable to negate. Must be convertible to bool.\n    \"\"\"\n\n    _loopable = True\n    _groupable = False\n\n    def _transform(self, var):\n        return ~var.astype(bool)\n\n\nclass Or_(Transformation):\n    \"\"\"Logical OR (inclusive) on two or more variables.\n\n    Parameters\n    ----------\n    dfs : list of :obj:`pandas.DataFrame`\n        variables to enter into the disjunction.\n    \"\"\"\n\n    _loopable = False\n    _groupable = False\n    _output_required = True\n    _aligned_required = True\n\n    def _transform(self, dfs):\n        df = pd.concat(dfs, axis=1, sort=True)\n        return df.any(axis=1).astype(int)\n\n\nclass Lag(Transformation):\n    \"\"\"Lag variable\n\n    Returns a variable that is lagged by a specified number of time points.\n    Spline interpolation of the requested ``order`` is used for non-integer\n    shifts.\n    Points outside the input are filled according to the given ``mode``.\n    Negative shifts move values toward the beginning of the sequence.\n    If ``difference`` is ``True``, the backward difference is calculated for\n    positive shifts, and the forward difference is calculated for negative\n    shifts.\n\n    Additional ``mode``s may be defined if there is need for them.\n    The ``scipy.ndimage.shift`` method provides the reference implementation\n    for all current modes. \"Constant\" is equivalent to the shift parameter\n    \"cval\".\n\n\n    Parameters\n    ----------\n    var : :obj:`numpy.ndarray`\n        variable to lag\n    shift : float, optional\n        number of places to shift the values (default: 1)\n    order : int, optional\n        order of spline interpolation, from 0-5 (default: 3)\n    mode : string\n        the `mode` parameter determines how the input array is extended\n        beyond its boundaries. Default is 'nearest'.\n        The following values are accepted:\n\n        \"nearest\" (a a a a | a b c d | d d d d)\n            The input is extended by replicating the boundary values\n        \"reflect\" (d c b a | a b c d | d c b a)\n            The input is extended by reflecting the array over the edge\n        \"constant\" (k k k k | a b c d | k k k k)\n            The input is extended by filling all values beyond the edge\n            with the same constant value, defined by the ``constant`` parameter\n    constant : float, optional\n        value to fill past edges of input if ``mode`` is ``\"constant\"``.\n        (default: 0)\n    difference : boolean, optional\n        Calculate the backward (if shift is positive) or forward (if shift is\n        negative) difference.\n        For the forward difference dx of an array x, dx[i] = x[i+1] - x[i].\n        For the backward difference dx of an array x, dx[i] = x[i] - x[i-1].\n        (default: ``False``)\n    \"\"\"\n\n    _input_type = 'numpy'\n    _return_type = 'numpy'\n\n    def _transform(self, var, shift=1, order=3, mode=\"nearest\",\n                   constant=0.0, difference=False):\n        var = var.flatten()\n        shifted = ndi.shift(var, shift=shift, order=order, mode=mode,\n                            cval=constant)\n        if not difference:\n            return shifted\n        elif shift >= 0:\n            return var - shifted\n        else:\n            return shifted - var\n", "path": "bids/modeling/transformations/compute.py"}, {"content": "from .compute import (Sum, Product, Scale, Orthogonalize, Threshold, And_, Or_,\n                      Not, Demean, Convolve, Lag)\nfrom .munge import (Split, Rename, Assign, Copy, Factor, Filter, Select,\n                    Delete, DropNA, Replace, ToDense, Group, Resample)\nfrom .base import TransformerManager\n\nAnd = And_\nOr = Or_\n\n__all__ = [\n    'And',\n    'And_',\n    'Assign',\n    'Convolve',\n    'Copy',\n    'Demean',\n    'Delete',\n    'DropNA',\n    'Factor',\n    'Filter',\n    'Group',\n    'Lag',\n    'Not',\n    'Or',\n    'Or_',\n    'Orthogonalize',\n    'Product',\n    'Rename',\n    'Replace',\n    'Resample',\n    'Scale',\n    'Select',\n    'Split',\n    'Sum',\n    'Threshold',\n    'ToDense',\n    'TransformerManager'\n]\n", "path": "bids/modeling/transformations/__init__.py"}]}