corems.chroma_peak.calc.subset

  1# This file contains functions for subsetting dataframes that contain mass feature data.
  2# This is based on the deimos package, found here: https://github.com/pnnl/deimos/blob/master/deimos/subset.py with some modifications.
  3
  4import multiprocessing as mp
  5from functools import partial
  6
  7import numpy as np
  8import pandas as pd
  9
 10class MultiSamplePartitions:
 11    '''
 12    Generator object that will lazily build and return each partition constructed
 13    from multiple samples.
 14
 15    Attributes
 16    ----------
 17    features : :obj:`~pandas.DataFrame`
 18        Input feature coordinates and intensities.
 19    split_on : str
 20        Dimension to partition the data.
 21    size : int
 22        Target partition size.
 23    tol : float
 24        Largest allowed distance between unique `split_on` observations.
 25    n_partitions : int
 26        Number of partitions in the data.
 27
 28    '''
 29
 30    def __init__(self,
 31                 features, 
 32                 split_on: str = 'mz', 
 33                 size: int = 500, 
 34                 tol: float = 25E-6, 
 35                 relative: bool = False):
 36        '''
 37        Initialize :obj:`~deimos.subset.Partitions` instance.
 38
 39        Parameters
 40        ----------
 41        features : :obj:`~pandas.DataFrame`
 42            Input feature coordinates and intensities.
 43        split_on : str
 44            Dimension to partition the data.
 45        size : int
 46            Target partition size.
 47        tol : float
 48            Largest allowed distance between unique `split_on` observations.
 49
 50        '''
 51        if not isinstance(split_on, str):
 52            raise TypeError(f"Expected 'split_on' to be a string, got {type(split_on).__name__}")
 53        if not isinstance(size, int):
 54            raise TypeError(f"Expected 'size' to be an integer, got {type(size).__name__}")
 55        if not isinstance(tol, float):
 56            raise TypeError(f"Expected 'tol' to be a float, got {type(tol).__name__}")
 57        if not isinstance(relative, bool):
 58            raise TypeError(f"Expected 'relative' to be a boolean, got {type(relative).__name__}")
 59
 60        self.features = features
 61        self.split_on = split_on
 62        self.size = size
 63        self.tol = tol
 64        self.relative = relative
 65
 66        self._compute_splits()
 67
 68    def _compute_splits(self):
 69        '''
 70        Determines data splits for partitioning.
 71
 72        '''
 73
 74        self.counter = 0
 75
 76        idx = self.features.groupby(by=self.split_on).size().sort_index()
 77
 78        counts = idx.values
 79        idx = idx.index
 80
 81        if self.relative:
 82            dxs = np.diff(idx) / idx[:-1]
 83        else:
 84            dxs = np.diff(idx)
 85
 86        # if relative, convert tol to absolute
 87        bins = []
 88        current_count = counts[0]
 89        current_bin = [idx[0]]
 90        self._counts = []
 91
 92        for i, dx in zip(range(1, len(idx)), dxs):
 93            if (current_count + counts[i] <= self.size) or (dx <= self.tol):
 94                current_bin.append(idx[i])
 95                current_count += counts[i]
 96
 97            else:
 98                bins.append(np.array(current_bin))
 99                self._counts.append(current_count)
100
101                current_bin = [idx[i]]
102                current_count = counts[i]
103
104        # Add last unadded bin
105        bins.append(np.array(current_bin))
106        self._counts.append(current_count)
107
108        self.bounds = np.array([[x.min(), x.max()] for x in bins])
109
110        # Number of partitions in the data
111        self.n_partitions = len(bins)
112
113    def __iter__(self):
114        return self
115
116    def __next__(self):
117        if self.counter < len(self.bounds):
118            q = '({} >= {}) & ({} <= {})'.format(self.split_on,
119                                                 self.bounds[self.counter][0],
120                                                 self.split_on,
121                                                 self.bounds[self.counter][1])
122
123            subset = self.features.query(q)
124
125            self.counter += 1
126            if len(subset.index) > 1:
127                return subset
128            else:
129                return None
130
131        raise StopIteration
132
133    def map(self, func, processes=1, **kwargs):
134        '''
135        Maps `func` to each partition, then returns the combined result.
136
137        Parameters
138        ----------
139        func : function
140            Function to apply to partitions.
141        processes : int
142            Number of parallel processes. If less than 2, a serial mapping is
143            applied.
144        kwargs
145            Keyword arguments passed to `func`.
146
147        Returns
148        -------
149        :obj:`~pandas.DataFrame`
150            Combined result of `func` applied to partitions.
151
152        '''
153
154        # Serial
155        if processes < 2:
156            result = [func(x, **kwargs) for x in self]
157
158        # Parallel
159        else:
160            with mp.Pool(processes=processes) as p:
161                result = list(p.imap(partial(func, **kwargs), self))
162
163        # Add partition index
164        for i in range(len(result)):
165            if result[i] is not None:
166                result[i]['partition_idx'] = i
167
168        # Combine partitions
169        return pd.concat(result, ignore_index=True)
170
171def multi_sample_partition(features, split_on='mz', size=500, tol=25E-6, relative=True):
172    '''
173    Partitions data along a given dimension. For use with features across
174    multiple samples, e.g. in alignment.
175
176    Parameters
177    ----------
178    features : :obj:`~pandas.DataFrame`
179        Input feature coordinates and intensities.
180    split_on : str
181        Dimension to partition the data.
182    size : int
183        Target partition size.
184    tol : float
185        Largest allowed distance between unique `split_on` observations.
186    relative : bool
187        If `True`, the `tol` parameter is interpreted as a relative tolerance.
188
189    Returns
190    -------
191    :obj:`~deimos.subset.Partitions`
192        A generator object that will lazily build and return each partition.
193
194    '''
195
196    return MultiSamplePartitions(features, split_on, size, tol, relative)
class MultiSamplePartitions:
 11class MultiSamplePartitions:
 12    '''
 13    Generator object that will lazily build and return each partition constructed
 14    from multiple samples.
 15
 16    Attributes
 17    ----------
 18    features : :obj:`~pandas.DataFrame`
 19        Input feature coordinates and intensities.
 20    split_on : str
 21        Dimension to partition the data.
 22    size : int
 23        Target partition size.
 24    tol : float
 25        Largest allowed distance between unique `split_on` observations.
 26    n_partitions : int
 27        Number of partitions in the data.
 28
 29    '''
 30
 31    def __init__(self,
 32                 features, 
 33                 split_on: str = 'mz', 
 34                 size: int = 500, 
 35                 tol: float = 25E-6, 
 36                 relative: bool = False):
 37        '''
 38        Initialize :obj:`~deimos.subset.Partitions` instance.
 39
 40        Parameters
 41        ----------
 42        features : :obj:`~pandas.DataFrame`
 43            Input feature coordinates and intensities.
 44        split_on : str
 45            Dimension to partition the data.
 46        size : int
 47            Target partition size.
 48        tol : float
 49            Largest allowed distance between unique `split_on` observations.
 50
 51        '''
 52        if not isinstance(split_on, str):
 53            raise TypeError(f"Expected 'split_on' to be a string, got {type(split_on).__name__}")
 54        if not isinstance(size, int):
 55            raise TypeError(f"Expected 'size' to be an integer, got {type(size).__name__}")
 56        if not isinstance(tol, float):
 57            raise TypeError(f"Expected 'tol' to be a float, got {type(tol).__name__}")
 58        if not isinstance(relative, bool):
 59            raise TypeError(f"Expected 'relative' to be a boolean, got {type(relative).__name__}")
 60
 61        self.features = features
 62        self.split_on = split_on
 63        self.size = size
 64        self.tol = tol
 65        self.relative = relative
 66
 67        self._compute_splits()
 68
 69    def _compute_splits(self):
 70        '''
 71        Determines data splits for partitioning.
 72
 73        '''
 74
 75        self.counter = 0
 76
 77        idx = self.features.groupby(by=self.split_on).size().sort_index()
 78
 79        counts = idx.values
 80        idx = idx.index
 81
 82        if self.relative:
 83            dxs = np.diff(idx) / idx[:-1]
 84        else:
 85            dxs = np.diff(idx)
 86
 87        # if relative, convert tol to absolute
 88        bins = []
 89        current_count = counts[0]
 90        current_bin = [idx[0]]
 91        self._counts = []
 92
 93        for i, dx in zip(range(1, len(idx)), dxs):
 94            if (current_count + counts[i] <= self.size) or (dx <= self.tol):
 95                current_bin.append(idx[i])
 96                current_count += counts[i]
 97
 98            else:
 99                bins.append(np.array(current_bin))
100                self._counts.append(current_count)
101
102                current_bin = [idx[i]]
103                current_count = counts[i]
104
105        # Add last unadded bin
106        bins.append(np.array(current_bin))
107        self._counts.append(current_count)
108
109        self.bounds = np.array([[x.min(), x.max()] for x in bins])
110
111        # Number of partitions in the data
112        self.n_partitions = len(bins)
113
114    def __iter__(self):
115        return self
116
117    def __next__(self):
118        if self.counter < len(self.bounds):
119            q = '({} >= {}) & ({} <= {})'.format(self.split_on,
120                                                 self.bounds[self.counter][0],
121                                                 self.split_on,
122                                                 self.bounds[self.counter][1])
123
124            subset = self.features.query(q)
125
126            self.counter += 1
127            if len(subset.index) > 1:
128                return subset
129            else:
130                return None
131
132        raise StopIteration
133
134    def map(self, func, processes=1, **kwargs):
135        '''
136        Maps `func` to each partition, then returns the combined result.
137
138        Parameters
139        ----------
140        func : function
141            Function to apply to partitions.
142        processes : int
143            Number of parallel processes. If less than 2, a serial mapping is
144            applied.
145        kwargs
146            Keyword arguments passed to `func`.
147
148        Returns
149        -------
150        :obj:`~pandas.DataFrame`
151            Combined result of `func` applied to partitions.
152
153        '''
154
155        # Serial
156        if processes < 2:
157            result = [func(x, **kwargs) for x in self]
158
159        # Parallel
160        else:
161            with mp.Pool(processes=processes) as p:
162                result = list(p.imap(partial(func, **kwargs), self))
163
164        # Add partition index
165        for i in range(len(result)):
166            if result[i] is not None:
167                result[i]['partition_idx'] = i
168
169        # Combine partitions
170        return pd.concat(result, ignore_index=True)

Generator object that will lazily build and return each partition constructed from multiple samples.

Attributes
  • features (~pandas.DataFrame): Input feature coordinates and intensities.
  • split_on (str): Dimension to partition the data.
  • size (int): Target partition size.
  • tol (float): Largest allowed distance between unique split_on observations.
  • n_partitions (int): Number of partitions in the data.
MultiSamplePartitions( features, split_on: str = 'mz', size: int = 500, tol: float = 2.5e-05, relative: bool = False)
31    def __init__(self,
32                 features, 
33                 split_on: str = 'mz', 
34                 size: int = 500, 
35                 tol: float = 25E-6, 
36                 relative: bool = False):
37        '''
38        Initialize :obj:`~deimos.subset.Partitions` instance.
39
40        Parameters
41        ----------
42        features : :obj:`~pandas.DataFrame`
43            Input feature coordinates and intensities.
44        split_on : str
45            Dimension to partition the data.
46        size : int
47            Target partition size.
48        tol : float
49            Largest allowed distance between unique `split_on` observations.
50
51        '''
52        if not isinstance(split_on, str):
53            raise TypeError(f"Expected 'split_on' to be a string, got {type(split_on).__name__}")
54        if not isinstance(size, int):
55            raise TypeError(f"Expected 'size' to be an integer, got {type(size).__name__}")
56        if not isinstance(tol, float):
57            raise TypeError(f"Expected 'tol' to be a float, got {type(tol).__name__}")
58        if not isinstance(relative, bool):
59            raise TypeError(f"Expected 'relative' to be a boolean, got {type(relative).__name__}")
60
61        self.features = features
62        self.split_on = split_on
63        self.size = size
64        self.tol = tol
65        self.relative = relative
66
67        self._compute_splits()

Initialize ~deimos.subset.Partitions instance.

Parameters
  • features (~pandas.DataFrame): Input feature coordinates and intensities.
  • split_on (str): Dimension to partition the data.
  • size (int): Target partition size.
  • tol (float): Largest allowed distance between unique split_on observations.
features
split_on
size
tol
relative
def map(self, func, processes=1, **kwargs):
134    def map(self, func, processes=1, **kwargs):
135        '''
136        Maps `func` to each partition, then returns the combined result.
137
138        Parameters
139        ----------
140        func : function
141            Function to apply to partitions.
142        processes : int
143            Number of parallel processes. If less than 2, a serial mapping is
144            applied.
145        kwargs
146            Keyword arguments passed to `func`.
147
148        Returns
149        -------
150        :obj:`~pandas.DataFrame`
151            Combined result of `func` applied to partitions.
152
153        '''
154
155        # Serial
156        if processes < 2:
157            result = [func(x, **kwargs) for x in self]
158
159        # Parallel
160        else:
161            with mp.Pool(processes=processes) as p:
162                result = list(p.imap(partial(func, **kwargs), self))
163
164        # Add partition index
165        for i in range(len(result)):
166            if result[i] is not None:
167                result[i]['partition_idx'] = i
168
169        # Combine partitions
170        return pd.concat(result, ignore_index=True)

Maps func to each partition, then returns the combined result.

Parameters
  • func (function): Function to apply to partitions.
  • processes (int): Number of parallel processes. If less than 2, a serial mapping is applied.
  • kwargs: Keyword arguments passed to func.
Returns
  • ~pandas.DataFrame: Combined result of func applied to partitions.
def multi_sample_partition(features, split_on='mz', size=500, tol=2.5e-05, relative=True):
172def multi_sample_partition(features, split_on='mz', size=500, tol=25E-6, relative=True):
173    '''
174    Partitions data along a given dimension. For use with features across
175    multiple samples, e.g. in alignment.
176
177    Parameters
178    ----------
179    features : :obj:`~pandas.DataFrame`
180        Input feature coordinates and intensities.
181    split_on : str
182        Dimension to partition the data.
183    size : int
184        Target partition size.
185    tol : float
186        Largest allowed distance between unique `split_on` observations.
187    relative : bool
188        If `True`, the `tol` parameter is interpreted as a relative tolerance.
189
190    Returns
191    -------
192    :obj:`~deimos.subset.Partitions`
193        A generator object that will lazily build and return each partition.
194
195    '''
196
197    return MultiSamplePartitions(features, split_on, size, tol, relative)

Partitions data along a given dimension. For use with features across multiple samples, e.g. in alignment.

Parameters
  • features (~pandas.DataFrame): Input feature coordinates and intensities.
  • split_on (str): Dimension to partition the data.
  • size (int): Target partition size.
  • tol (float): Largest allowed distance between unique split_on observations.
  • relative (bool): If True, the tol parameter is interpreted as a relative tolerance.
Returns
  • ~deimos.subset.Partitions: A generator object that will lazily build and return each partition.