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