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_onobservations. - 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_onobservations.
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 offuncapplied 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_onobservations. - relative (bool):
If
True, thetolparameter is interpreted as a relative tolerance.
Returns
~deimos.subset.Partitions: A generator object that will lazily build and return each partition.