cap-anndata 0.1.1__tar.gz → 0.2.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cap_anndata
3
- Version: 0.1.1
4
- Summary: Partial read of AnnData files for low-memory operations with large datasets.
3
+ Version: 0.2.1
4
+ Summary: Partial read/write of AnnData (h5ad) files for low-memory operations with large datasets.
5
5
  Home-page: https://github.com/cellannotation/cap-anndata
6
6
  Author: R. Mukhin, A. Isaev
7
7
  Author-email: roman@ebookapplications.com
@@ -15,8 +15,10 @@ License-File: LICENSE
15
15
  Requires-Dist: numpy>=1.26.3
16
16
  Requires-Dist: pandas>=2.2.0
17
17
  Requires-Dist: anndata>=0.10.5
18
+ Requires-Dist: h5py>=3.5.0
18
19
  Provides-Extra: dev
19
20
  Requires-Dist: pytest>=8.0.0; extra == "dev"
21
+ Requires-Dist: setuptools~=69.1.1; extra == "dev"
20
22
 
21
23
  # CAP-AnnData: Enhanced Partial I/O for AnnData Files
22
24
 
@@ -25,41 +27,65 @@ CAP-AnnData enriches the AnnData ecosystem by offering tailored functionalities
25
27
 
26
28
  ## Getting Started
27
29
 
30
+ ### Installation
31
+ Install CAP-AnnData via pip:
32
+
33
+ ```commandline
34
+ pip install -U cap-anndata
35
+ ```
36
+
28
37
  ### Running Tests
29
- Ensure the integrity and reliability of CAP-AnnData on your system by running the unit tests in `test/unit_test.py`.
38
+ Ensure the integrity and reliability of CAP-AnnData on your system by running the unit tests via `pytest` from the root of the repo.
39
+
40
+ ```commandline
41
+ pip install pytest
42
+ pytest test
43
+ ```
30
44
 
31
45
  Make sure Python 3.9 or newer is used, along with all requirements specified in requirements.txt
32
46
 
33
47
  ## How-TO:
34
48
 
35
- #### 1. Read AnnData File Dataframes
49
+ #### 1. Access AnnData File DataFrames
36
50
 
37
51
  ##### Basic Reading
38
52
  By default, `CapAnnData` does not automatically read any data. To begin working with dataframes, you need to explicitly read the data from the AnnData file. You can read the entire dataframe or select specific columns. For partial reading, provide a list of column names.
39
53
 
40
54
  ```python
41
- import h5py
42
- from cap_anndata import CapAnnData
55
+ from cap_anndata import read_h5ad
43
56
 
44
57
  file_path = "your_data.h5ad"
45
- with h5py.File(file_path, 'r') as file:
46
- cap_adata = CapAnnData(file)
47
-
58
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
59
+ # Get the list of all obs columns in AnnData file
60
+ cap_adata.obs_keys() # ['a', 'b', 'c']
48
61
  # Read all columns of 'obs'
49
62
  cap_adata.read_obs()
63
+ # Get the list of columns of DataFrame in memory
64
+ cap_adata.obs.columns # ['a', 'b', 'c']
50
65
 
66
+ # Get the list of all var columns in AnnData file
67
+ cap_adata.var_keys() # ['d', 'e', 'f']
51
68
  # Read specific columns of 'var'
52
- cap_adata.read_var(columns=['gene_expression', 'dispersion'])
53
-
54
- # Read all columns of raw.var
55
- cap_adata.read_var(raw=True)
69
+ cap_adata.read_var(columns=['d'])
70
+ cap_adata.var.columns # ['d']
71
+ # Read additional column
72
+ cap_adata.read_var(columns=['e'])
73
+ cap_adata.var.columns # ['d', 'e']
74
+
75
+ # Read column and reset the in-memory DataFrame before that
76
+ cap_adata.read_var(columns=['f'], reset=True)
77
+ cap_adata.var.columns # ['f']
78
+
79
+ # Read no columns of raw.var (only the index)
80
+ cap_adata.raw.read_var(columns=[])
56
81
  ```
57
82
 
58
- ##### Non-existing columns
83
+ ##### Difference between `obs_keys()` and `obs.columns`
84
+ `obs_keys()` returns the list of columns in the on-disc AnnData file, while `obs.columns` returns the list of columns in the in-memory DataFrame. The two lists may differ if you read only specific columns. If you modify the in-memory DataFrame, the `obs_keys()` will reflect the changes. BTW it is recommended to check the `obs_keys()` before the `overwrite()` call to avoid the AnnData file damage.
59
85
 
60
- If a column doesn't exist in the file, no error will be raised but the column will be missing in the resulting Dataframe. So, the list of columns saying more like "try to read this columns from the file". It is needed because we there is no way yet to check if the column exists before the read.
86
+ If a column doesn't exist in the file, no error will be raised but the column will be missing in the resulting DataFrame. So, the list of columns saying more like "try to read this columns from the file". It is needed because we there is no way yet to check if the column exists before the read. Exactly the same behavior is for the `var_keys()` and `var.columns`.
61
87
 
62
- #### 2. Modify the AnnData File Dataframes In-Place
88
+ #### 2. Modify the AnnData File DataFrames In-Place
63
89
 
64
90
  You can directly modify the dataframe by adding, renaming, or removing columns.
65
91
 
@@ -68,13 +94,14 @@ You can directly modify the dataframe by adding, renaming, or removing columns.
68
94
  cap_adata.obs['new_col'] = [value1, value2, value3]
69
95
 
70
96
  # Rename a column
71
- cap_adata.rename_column('old_col_name', 'new_col_name')
97
+ cap_adata.obs.rename_column('old_col_name', 'new_col_name')
72
98
 
73
99
  # Remove a column
74
- cap_adata.remove_column('col_to_remove')
100
+ cap_adata.obs.remove_column('col_to_remove')
75
101
  ```
76
102
 
77
103
  After modifications, you can overwrite the changes back to the AnnData file. If a value doesn't exist, it will be created.
104
+ Note: `read_h5ad` must be called with `edit=True` argument to open `.h5ad` file in `r+` mode.
78
105
 
79
106
  ```python
80
107
  # overwrite all values which were read
@@ -84,7 +111,7 @@ cap_adata.overwrite()
84
111
  cap_adata.overwrite(['obs', 'var'])
85
112
  ```
86
113
 
87
- The full list of supported fields: `X`, `raw.X`, `obs`, `var`, `raw.var`, `obsm`, `uns`.
114
+ The full list of supported fields: `obs`, `var`, `raw.var`, `obsm`, `uns`.
88
115
 
89
116
  #### 3. How to Read Few Columns but Overwrite One in a Dataframe
90
117
 
@@ -100,14 +127,19 @@ cap_adata.obs.drop(columns='sample', inplace=True)
100
127
 
101
128
  # Overwrite changes
102
129
  cap_adata.overwrite(['obs'])
130
+
131
+ # NOTE that the line
132
+ # cap_adata.read_obs(columns=['sample'], reset=True)
133
+ # Will override in-memory changes with values from the AnnData file
103
134
  ```
104
135
 
105
136
  #### 4. How to work with X and raw.X
106
137
 
107
- The CapAnnData package won't read any field by default. However, the `X` and `raw.X` will be linked to the backed matrices automatically upon the first request to those fields.
138
+ The CapAnnData package won't read any field by default. However, the `X` and `raw.X` will be linked to the backed matrices automatically upon the first request to those fields.
139
+ The X object will be returned as the `h5py.Dataset` or `AnnData.experimental.sparse_dataset`.
108
140
 
109
141
  ```python
110
- with h5py.File(path) as file:
142
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
111
143
  # self.X is None here
112
144
  cap_adata = CapAnnData(file)
113
145
 
@@ -135,13 +167,13 @@ s_ = np.s_[mask, :5]
135
167
 
136
168
  #### 5. How to handle obsm embeddings matrixes
137
169
 
138
- By the default the CapAnnData will not read the embeddings matrix. The link to the h5py objects will be created upon the first call of the `.obsm` property. Alike the AnnData package the call like `cap_adata.obsm["X_tsne"]` will not return the in-memory matrix but will return the backed version instead. We can get the information about the name and shape of the embeddings without taking the whole matrixes in the memory!
170
+ By the default the CapAnnData will not read the embeddings matrix.
171
+ The link to the h5py objects will be created upon the first call of the `.obsm` property.
172
+ Alike the AnnData package the call like `cap_adata.obsm["X_tsne"]` will not return the in-memory matrix but will return the backed version instead.
173
+ It is possible to get the information about the name and shape of the embeddings without taking the whole matrix in the memory.
139
174
 
140
175
  ```python
141
- with h5py.File(path) as file:
142
- # initialization
143
- cap_adata = CapAnnData(file)
144
-
176
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
145
177
  # will return the list of strings
146
178
  obsm_keys = cap_adata.obsm_keys()
147
179
 
@@ -158,10 +190,7 @@ with h5py.File(path) as file:
158
190
  The `CapAnnData` class will lazely link the uns section upon the first call but ***WILL NOT*** read it into memory. Instead, the dictionary of the pairs `{'key': "__NotLinkedObject"}` will be creted. It allow to get the list of keys before the actual read. To read the uns section in the memory the `.read_uns(keys)` method must be called.
159
191
 
160
192
  ```python
161
- with h5py.File(path) as file:
162
- # initialization
163
- cap_adata = CapAnnData(file)
164
-
193
+ with read_h5ad(file_path=file_path, edit=True) as cap_adata:
165
194
  # will return the keys() object
166
195
  keys = cap_adata.uns.keys()
167
196
 
@@ -197,3 +226,28 @@ To save `uns` changes the method `CapAnnData.overwrite()` must be called.
197
226
  cap_adata.overwrite() # all in-memory fields will be overwritten
198
227
  cap_adata.overwrite(["uns"]) # overwrite the uns secion only
199
228
  ```
229
+
230
+ #### 7. Join and Merge DataFrames
231
+
232
+ Cap-AnnData provides enhanced methods for joining and merging dataframes, preserving column order and data integrity
233
+
234
+ ```python
235
+ from cap_anndata import CapAnnDataDF
236
+ import pandas as pd
237
+
238
+ data1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
239
+ data2 = pd.DataFrame({'D': [7, 8, 9], 'E': [10, 11, 12]})
240
+ cap_anndata_df1 = CapAnnDataDF.from_df(data1, column_order=['A', 'B', 'C'])
241
+
242
+ cap_df = cap_anndata_df1.join(data2, how='left')
243
+
244
+ cap_df.columns # ['A', 'B', 'D', 'E']
245
+ cap_df.column_order # ['A', 'B', 'C', 'D', 'E']
246
+
247
+ data3 = pd.DataFrame({'A': [2, 3, 4], 'D': [10, 11, 12]})
248
+ cap_df = cap_anndata_df1.merge(data3, on='A')
249
+
250
+ cap_df.columns # ['A', 'B', 'D']
251
+ cap_df.column_order # ['A', 'B', 'C', 'D']
252
+ cap_df.shape # (2, 3)
253
+ ```
@@ -5,41 +5,65 @@ CAP-AnnData enriches the AnnData ecosystem by offering tailored functionalities
5
5
 
6
6
  ## Getting Started
7
7
 
8
+ ### Installation
9
+ Install CAP-AnnData via pip:
10
+
11
+ ```commandline
12
+ pip install -U cap-anndata
13
+ ```
14
+
8
15
  ### Running Tests
9
- Ensure the integrity and reliability of CAP-AnnData on your system by running the unit tests in `test/unit_test.py`.
16
+ Ensure the integrity and reliability of CAP-AnnData on your system by running the unit tests via `pytest` from the root of the repo.
17
+
18
+ ```commandline
19
+ pip install pytest
20
+ pytest test
21
+ ```
10
22
 
11
23
  Make sure Python 3.9 or newer is used, along with all requirements specified in requirements.txt
12
24
 
13
25
  ## How-TO:
14
26
 
15
- #### 1. Read AnnData File Dataframes
27
+ #### 1. Access AnnData File DataFrames
16
28
 
17
29
  ##### Basic Reading
18
30
  By default, `CapAnnData` does not automatically read any data. To begin working with dataframes, you need to explicitly read the data from the AnnData file. You can read the entire dataframe or select specific columns. For partial reading, provide a list of column names.
19
31
 
20
32
  ```python
21
- import h5py
22
- from cap_anndata import CapAnnData
33
+ from cap_anndata import read_h5ad
23
34
 
24
35
  file_path = "your_data.h5ad"
25
- with h5py.File(file_path, 'r') as file:
26
- cap_adata = CapAnnData(file)
27
-
36
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
37
+ # Get the list of all obs columns in AnnData file
38
+ cap_adata.obs_keys() # ['a', 'b', 'c']
28
39
  # Read all columns of 'obs'
29
40
  cap_adata.read_obs()
41
+ # Get the list of columns of DataFrame in memory
42
+ cap_adata.obs.columns # ['a', 'b', 'c']
30
43
 
44
+ # Get the list of all var columns in AnnData file
45
+ cap_adata.var_keys() # ['d', 'e', 'f']
31
46
  # Read specific columns of 'var'
32
- cap_adata.read_var(columns=['gene_expression', 'dispersion'])
33
-
34
- # Read all columns of raw.var
35
- cap_adata.read_var(raw=True)
47
+ cap_adata.read_var(columns=['d'])
48
+ cap_adata.var.columns # ['d']
49
+ # Read additional column
50
+ cap_adata.read_var(columns=['e'])
51
+ cap_adata.var.columns # ['d', 'e']
52
+
53
+ # Read column and reset the in-memory DataFrame before that
54
+ cap_adata.read_var(columns=['f'], reset=True)
55
+ cap_adata.var.columns # ['f']
56
+
57
+ # Read no columns of raw.var (only the index)
58
+ cap_adata.raw.read_var(columns=[])
36
59
  ```
37
60
 
38
- ##### Non-existing columns
61
+ ##### Difference between `obs_keys()` and `obs.columns`
62
+ `obs_keys()` returns the list of columns in the on-disc AnnData file, while `obs.columns` returns the list of columns in the in-memory DataFrame. The two lists may differ if you read only specific columns. If you modify the in-memory DataFrame, the `obs_keys()` will reflect the changes. BTW it is recommended to check the `obs_keys()` before the `overwrite()` call to avoid the AnnData file damage.
39
63
 
40
- If a column doesn't exist in the file, no error will be raised but the column will be missing in the resulting Dataframe. So, the list of columns saying more like "try to read this columns from the file". It is needed because we there is no way yet to check if the column exists before the read.
64
+ If a column doesn't exist in the file, no error will be raised but the column will be missing in the resulting DataFrame. So, the list of columns saying more like "try to read this columns from the file". It is needed because we there is no way yet to check if the column exists before the read. Exactly the same behavior is for the `var_keys()` and `var.columns`.
41
65
 
42
- #### 2. Modify the AnnData File Dataframes In-Place
66
+ #### 2. Modify the AnnData File DataFrames In-Place
43
67
 
44
68
  You can directly modify the dataframe by adding, renaming, or removing columns.
45
69
 
@@ -48,13 +72,14 @@ You can directly modify the dataframe by adding, renaming, or removing columns.
48
72
  cap_adata.obs['new_col'] = [value1, value2, value3]
49
73
 
50
74
  # Rename a column
51
- cap_adata.rename_column('old_col_name', 'new_col_name')
75
+ cap_adata.obs.rename_column('old_col_name', 'new_col_name')
52
76
 
53
77
  # Remove a column
54
- cap_adata.remove_column('col_to_remove')
78
+ cap_adata.obs.remove_column('col_to_remove')
55
79
  ```
56
80
 
57
81
  After modifications, you can overwrite the changes back to the AnnData file. If a value doesn't exist, it will be created.
82
+ Note: `read_h5ad` must be called with `edit=True` argument to open `.h5ad` file in `r+` mode.
58
83
 
59
84
  ```python
60
85
  # overwrite all values which were read
@@ -64,7 +89,7 @@ cap_adata.overwrite()
64
89
  cap_adata.overwrite(['obs', 'var'])
65
90
  ```
66
91
 
67
- The full list of supported fields: `X`, `raw.X`, `obs`, `var`, `raw.var`, `obsm`, `uns`.
92
+ The full list of supported fields: `obs`, `var`, `raw.var`, `obsm`, `uns`.
68
93
 
69
94
  #### 3. How to Read Few Columns but Overwrite One in a Dataframe
70
95
 
@@ -80,14 +105,19 @@ cap_adata.obs.drop(columns='sample', inplace=True)
80
105
 
81
106
  # Overwrite changes
82
107
  cap_adata.overwrite(['obs'])
108
+
109
+ # NOTE that the line
110
+ # cap_adata.read_obs(columns=['sample'], reset=True)
111
+ # Will override in-memory changes with values from the AnnData file
83
112
  ```
84
113
 
85
114
  #### 4. How to work with X and raw.X
86
115
 
87
- The CapAnnData package won't read any field by default. However, the `X` and `raw.X` will be linked to the backed matrices automatically upon the first request to those fields.
116
+ The CapAnnData package won't read any field by default. However, the `X` and `raw.X` will be linked to the backed matrices automatically upon the first request to those fields.
117
+ The X object will be returned as the `h5py.Dataset` or `AnnData.experimental.sparse_dataset`.
88
118
 
89
119
  ```python
90
- with h5py.File(path) as file:
120
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
91
121
  # self.X is None here
92
122
  cap_adata = CapAnnData(file)
93
123
 
@@ -115,13 +145,13 @@ s_ = np.s_[mask, :5]
115
145
 
116
146
  #### 5. How to handle obsm embeddings matrixes
117
147
 
118
- By the default the CapAnnData will not read the embeddings matrix. The link to the h5py objects will be created upon the first call of the `.obsm` property. Alike the AnnData package the call like `cap_adata.obsm["X_tsne"]` will not return the in-memory matrix but will return the backed version instead. We can get the information about the name and shape of the embeddings without taking the whole matrixes in the memory!
148
+ By the default the CapAnnData will not read the embeddings matrix.
149
+ The link to the h5py objects will be created upon the first call of the `.obsm` property.
150
+ Alike the AnnData package the call like `cap_adata.obsm["X_tsne"]` will not return the in-memory matrix but will return the backed version instead.
151
+ It is possible to get the information about the name and shape of the embeddings without taking the whole matrix in the memory.
119
152
 
120
153
  ```python
121
- with h5py.File(path) as file:
122
- # initialization
123
- cap_adata = CapAnnData(file)
124
-
154
+ with read_h5ad(file_path=file_path, edit=False) as cap_adata:
125
155
  # will return the list of strings
126
156
  obsm_keys = cap_adata.obsm_keys()
127
157
 
@@ -138,10 +168,7 @@ with h5py.File(path) as file:
138
168
  The `CapAnnData` class will lazely link the uns section upon the first call but ***WILL NOT*** read it into memory. Instead, the dictionary of the pairs `{'key': "__NotLinkedObject"}` will be creted. It allow to get the list of keys before the actual read. To read the uns section in the memory the `.read_uns(keys)` method must be called.
139
169
 
140
170
  ```python
141
- with h5py.File(path) as file:
142
- # initialization
143
- cap_adata = CapAnnData(file)
144
-
171
+ with read_h5ad(file_path=file_path, edit=True) as cap_adata:
145
172
  # will return the keys() object
146
173
  keys = cap_adata.uns.keys()
147
174
 
@@ -177,3 +204,28 @@ To save `uns` changes the method `CapAnnData.overwrite()` must be called.
177
204
  cap_adata.overwrite() # all in-memory fields will be overwritten
178
205
  cap_adata.overwrite(["uns"]) # overwrite the uns secion only
179
206
  ```
207
+
208
+ #### 7. Join and Merge DataFrames
209
+
210
+ Cap-AnnData provides enhanced methods for joining and merging dataframes, preserving column order and data integrity
211
+
212
+ ```python
213
+ from cap_anndata import CapAnnDataDF
214
+ import pandas as pd
215
+
216
+ data1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
217
+ data2 = pd.DataFrame({'D': [7, 8, 9], 'E': [10, 11, 12]})
218
+ cap_anndata_df1 = CapAnnDataDF.from_df(data1, column_order=['A', 'B', 'C'])
219
+
220
+ cap_df = cap_anndata_df1.join(data2, how='left')
221
+
222
+ cap_df.columns # ['A', 'B', 'D', 'E']
223
+ cap_df.column_order # ['A', 'B', 'C', 'D', 'E']
224
+
225
+ data3 = pd.DataFrame({'A': [2, 3, 4], 'D': [10, 11, 12]})
226
+ cap_df = cap_anndata_df1.merge(data3, on='A')
227
+
228
+ cap_df.columns # ['A', 'B', 'D']
229
+ cap_df.column_order # ['A', 'B', 'C', 'D']
230
+ cap_df.shape # (2, 3)
231
+ ```
@@ -1,6 +1,10 @@
1
1
  from .backed_df import CapAnnDataDF
2
2
  from .backed_uns import CapAnnDataUns
3
3
  from .cap_anndata import CapAnnData
4
+ from .reader import (
5
+ read_directly,
6
+ read_h5ad,
7
+ )
4
8
 
5
9
 
6
10
  __all__ = ["CapAnnData"]
@@ -0,0 +1,69 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from typing import List, Any, Union
4
+ import logging
5
+
6
+ from pandas._typing import Self
7
+ from pandas.core.generic import bool_t
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class CapAnnDataDF(pd.DataFrame):
13
+ """
14
+ The class to expand the pandas DataFrame behaviour to support partial
15
+ reading and writing of AnnData obs and var (raw.var) fields.
16
+ The main feature of the class is handling <column-order> attribute
17
+ which must be a copy of h5py.Group attribute
18
+ """
19
+
20
+ _metadata = ["column_order"]
21
+
22
+ def rename_column(self, old_name: str, new_name: str) -> None:
23
+ i = np.where(self.column_order == old_name)[0]
24
+ self.column_order[i] = new_name
25
+ self.rename(columns={old_name: new_name}, inplace=True)
26
+
27
+ def remove_column(self, col_name: str) -> None:
28
+ i = np.where(self.column_order == col_name)[0]
29
+ self.column_order = np.delete(self.column_order, i)
30
+ self.drop(columns=[col_name], inplace=True)
31
+
32
+ def __setitem__(self, key, value) -> None:
33
+ if key not in self.column_order:
34
+ self.column_order = np.append(self.column_order, key)
35
+ return super().__setitem__(key, value)
36
+
37
+ @classmethod
38
+ def from_df(cls, df: pd.DataFrame, column_order: List[str] = None) -> Self:
39
+ if column_order is None:
40
+ column_order = df.columns.to_numpy()
41
+
42
+ new_inst = cls(df)
43
+ new_inst.column_order = column_order
44
+ return new_inst
45
+
46
+ def join(self, other: Any, **kwargs) -> Self:
47
+ result = super().join(other=other, **kwargs)
48
+ if isinstance(other, CapAnnDataDF):
49
+ new_columns = [
50
+ col for col in other.column_order if col not in self.column_order
51
+ ]
52
+ else:
53
+ new_columns = [col for col in other.columns if col not in self.column_order]
54
+ column_order = np.append(self.column_order, new_columns)
55
+ return self.from_df(result, column_order=column_order)
56
+
57
+ def merge(self, right, **kwargs) -> Self:
58
+ result = super().merge(right=right, **kwargs)
59
+ if isinstance(right, CapAnnDataDF):
60
+ new_columns = [
61
+ col for col in right.column_order if col not in self.column_order
62
+ ]
63
+ else:
64
+ new_columns = [col for col in right.columns if col not in self.column_order]
65
+ column_order = np.append(self.column_order, new_columns)
66
+ return self.from_df(result, column_order=column_order)
67
+
68
+ def copy(self, deep: Union[bool_t, None] = True) -> Self:
69
+ return self.from_df(super().copy(deep=deep), column_order=self.column_order)