pertpy 0.6.0__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. pertpy/__init__.py +4 -2
  2. pertpy/data/__init__.py +66 -1
  3. pertpy/data/_dataloader.py +28 -26
  4. pertpy/data/_datasets.py +261 -92
  5. pertpy/metadata/__init__.py +6 -0
  6. pertpy/metadata/_cell_line.py +795 -0
  7. pertpy/metadata/_compound.py +128 -0
  8. pertpy/metadata/_drug.py +238 -0
  9. pertpy/metadata/_look_up.py +569 -0
  10. pertpy/metadata/_metadata.py +70 -0
  11. pertpy/metadata/_moa.py +125 -0
  12. pertpy/plot/__init__.py +0 -13
  13. pertpy/preprocessing/__init__.py +2 -0
  14. pertpy/preprocessing/_guide_rna.py +89 -6
  15. pertpy/tools/__init__.py +48 -15
  16. pertpy/tools/_augur.py +329 -32
  17. pertpy/tools/_cinemaot.py +145 -6
  18. pertpy/tools/_coda/_base_coda.py +1237 -116
  19. pertpy/tools/_coda/_sccoda.py +66 -36
  20. pertpy/tools/_coda/_tasccoda.py +46 -39
  21. pertpy/tools/_dialogue.py +180 -77
  22. pertpy/tools/_differential_gene_expression/__init__.py +20 -0
  23. pertpy/tools/_differential_gene_expression/_base.py +657 -0
  24. pertpy/tools/_differential_gene_expression/_checks.py +41 -0
  25. pertpy/tools/_differential_gene_expression/_dge_comparison.py +86 -0
  26. pertpy/tools/_differential_gene_expression/_edger.py +125 -0
  27. pertpy/tools/_differential_gene_expression/_formulaic.py +189 -0
  28. pertpy/tools/_differential_gene_expression/_pydeseq2.py +95 -0
  29. pertpy/tools/_differential_gene_expression/_simple_tests.py +162 -0
  30. pertpy/tools/_differential_gene_expression/_statsmodels.py +72 -0
  31. pertpy/tools/_distances/_distance_tests.py +29 -24
  32. pertpy/tools/_distances/_distances.py +584 -98
  33. pertpy/tools/_enrichment.py +460 -0
  34. pertpy/tools/_kernel_pca.py +1 -1
  35. pertpy/tools/_milo.py +406 -49
  36. pertpy/tools/_mixscape.py +677 -55
  37. pertpy/tools/_perturbation_space/_clustering.py +10 -3
  38. pertpy/tools/_perturbation_space/_comparison.py +112 -0
  39. pertpy/tools/_perturbation_space/_discriminator_classifiers.py +524 -0
  40. pertpy/tools/_perturbation_space/_perturbation_space.py +146 -52
  41. pertpy/tools/_perturbation_space/_simple.py +52 -11
  42. pertpy/tools/_scgen/__init__.py +1 -1
  43. pertpy/tools/_scgen/_base_components.py +2 -3
  44. pertpy/tools/_scgen/_scgen.py +706 -0
  45. pertpy/tools/_scgen/_utils.py +3 -5
  46. pertpy/tools/decoupler_LICENSE +674 -0
  47. {pertpy-0.6.0.dist-info → pertpy-0.8.0.dist-info}/METADATA +48 -20
  48. pertpy-0.8.0.dist-info/RECORD +57 -0
  49. {pertpy-0.6.0.dist-info → pertpy-0.8.0.dist-info}/WHEEL +1 -1
  50. pertpy/plot/_augur.py +0 -234
  51. pertpy/plot/_cinemaot.py +0 -81
  52. pertpy/plot/_coda.py +0 -1001
  53. pertpy/plot/_dialogue.py +0 -91
  54. pertpy/plot/_guide_rna.py +0 -82
  55. pertpy/plot/_milopy.py +0 -284
  56. pertpy/plot/_mixscape.py +0 -594
  57. pertpy/plot/_scgen.py +0 -337
  58. pertpy/tools/_differential_gene_expression.py +0 -99
  59. pertpy/tools/_metadata/__init__.py +0 -0
  60. pertpy/tools/_metadata/_cell_line.py +0 -613
  61. pertpy/tools/_metadata/_look_up.py +0 -342
  62. pertpy/tools/_perturbation_space/_discriminator_classifier.py +0 -381
  63. pertpy/tools/_scgen/_jax_scgen.py +0 -370
  64. pertpy-0.6.0.dist-info/RECORD +0 -50
  65. /pertpy/tools/_scgen/{_jax_scgenvae.py → _scgenvae.py} +0 -0
  66. {pertpy-0.6.0.dist-info → pertpy-0.8.0.dist-info}/licenses/LICENSE +0 -0
pertpy/plot/_dialogue.py DELETED
@@ -1,91 +0,0 @@
1
- import matplotlib.pyplot as plt
2
- import pandas as pd
3
- import scanpy as sc
4
- import seaborn as sns
5
- from anndata import AnnData
6
- from seaborn import PairGrid
7
-
8
-
9
- class DialoguePlot:
10
- @staticmethod
11
- def split_violins(
12
- adata: AnnData,
13
- split_key: str,
14
- celltype_key=str,
15
- split_which: tuple[str, str] = None,
16
- mcp: str = "mcp_0",
17
- ) -> plt.Axes:
18
- """Plots split violin plots for a given MCP and split variable.
19
-
20
- Any cells with a value for split_key not in split_which are removed from the plot.
21
-
22
- Args:
23
- adata: Annotated data object.
24
- split_key: Variable in adata.obs used to split the data.
25
- celltype_key: Key for cell type annotations.
26
- split_which: Which values of split_key to plot. Required if more than 2 values in split_key.
27
- mcp: Key for MCP data. Defaults to "mcp_0".
28
-
29
- Returns:
30
- A :class:`~matplotlib.axes.Axes` object
31
-
32
- Examples:
33
- >>> import pertpy as pt
34
- >>> import scanpy as sc
35
- >>> adata = pt.dt.dialogue_example()
36
- >>> sc.pp.pca(adata)
37
- >>> dl = pt.tl.Dialogue(sample_id = "clinical.status", celltype_key = "cell.subtypes", \
38
- n_counts_key = "nCount_RNA", n_mpcs = 3)
39
- >>> adata, mcps, ws, ct_subs = dl.calculate_multifactor_PMD(adata, normalize=True)
40
- >>> pt.pl.dl.split_violins(adata, split_key='gender', celltype_key='cell.subtypes')
41
- """
42
- df = sc.get.obs_df(adata, [celltype_key, mcp, split_key])
43
- if split_which is None:
44
- split_which = df[split_key].unique()
45
- df = df[df[split_key].isin(split_which)]
46
- df[split_key] = df[split_key].cat.remove_unused_categories()
47
-
48
- ax = sns.violinplot(data=df, x=celltype_key, y=mcp, hue=split_key, split=True)
49
-
50
- ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
51
-
52
- return ax
53
-
54
- @staticmethod
55
- def pairplot(adata: AnnData, celltype_key: str, color: str, sample_id: str, mcp: str = "mcp_0") -> PairGrid:
56
- """Generate a pairplot visualization for multi-cell perturbation (MCP) data.
57
-
58
- Computes the mean of a specified MCP feature (mcp) for each combination of sample and cell type,
59
- then creates a pairplot to visualize the relationships between these mean MCP values.
60
-
61
- Args:
62
- adata: Annotated data object.
63
- celltype_key: Key in adata.obs containing cell type annotations.
64
- color: Key in adata.obs for color annotations. This parameter is used as the hue
65
- sample_id: Key in adata.obs for the sample annotations.
66
- mcp: Key in adata.obs for MCP feature values. Defaults to "mcp_0".
67
-
68
- Returns:
69
- Seaborn Pairgrid object.
70
-
71
- Examples:
72
- >>> import pertpy as pt
73
- >>> import scanpy as sc
74
- >>> adata = pt.dt.dialogue_example()
75
- >>> sc.pp.pca(adata)
76
- >>> dl = pt.tl.Dialogue(sample_id = "clinical.status", celltype_key = "cell.subtypes", \
77
- n_counts_key = "nCount_RNA", n_mpcs = 3)
78
- >>> adata, mcps, ws, ct_subs = dl.calculate_multifactor_PMD(adata, normalize=True)
79
- >>> pt.pl.dl.pairplot(adata, celltype_key="cell.subtypes", color="gender", sample_id="clinical.status")
80
- """
81
- mean_mcps = adata.obs.groupby([sample_id, celltype_key])[mcp].mean()
82
- mean_mcps = mean_mcps.reset_index()
83
- mcp_pivot = pd.pivot(mean_mcps[[sample_id, celltype_key, mcp]], index=sample_id, columns=celltype_key)[mcp]
84
-
85
- aggstats = adata.obs.groupby([sample_id])[color].describe()
86
- aggstats = aggstats.loc[list(mcp_pivot.index), :]
87
- aggstats[color] = aggstats["top"]
88
- mcp_pivot = pd.concat([mcp_pivot, aggstats[color]], axis=1)
89
- ax = sns.pairplot(mcp_pivot, hue=color, corner=True)
90
-
91
- return ax
pertpy/plot/_guide_rna.py DELETED
@@ -1,82 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- import numpy as np
6
- import scanpy as sc
7
-
8
- if TYPE_CHECKING:
9
- from anndata import AnnData
10
- from matplotlib.axes import Axes
11
-
12
-
13
- class GuideRnaPlot:
14
- @staticmethod
15
- def heatmap(
16
- adata: AnnData,
17
- layer: str | None = None,
18
- order_by: np.ndarray | str | None = None,
19
- key_to_save_order: str = None,
20
- **kwds,
21
- ) -> list[Axes]:
22
- """Heatmap plotting of guide RNA expression matrix.
23
-
24
- Assuming guides have sparse expression, this function reorders cells
25
- and plots guide RNA expression so that a nice sparse representation is achieved.
26
- The cell ordering can be stored and reused in future plots to obtain consistent
27
- plots before and after analysis of the guide RNA expression.
28
- Note: This function expects a log-normalized or binary data.
29
-
30
- Args:
31
- adata: Annotated data matrix containing gRNA values
32
- layer: Key to the layer containing log normalized count values of the gRNAs.
33
- adata.X is used if layer is None.
34
- order_by: The order of cells in y axis. Defaults to None.
35
- If None, cells will be reordered to have a nice sparse representation.
36
- If a string is provided, adata.obs[order_by] will be used as the order.
37
- If a numpy array is provided, the array will be used for ordering.
38
- key_to_save_order: The obs key to save cell orders in the current plot. Only saves if not None.
39
- kwds: Are passed to sc.pl.heatmap.
40
-
41
- Returns:
42
- List of Axes. Alternatively you can pass save or show parameters as they will be passed to sc.pl.heatmap.
43
- Order of cells in the y axis will be saved on adata.obs[key_to_save_order] if provided.
44
-
45
- Examples:
46
- Each cell is assigned to gRNA that occurs at least 5 times in the respective cell, which is then
47
- visualized using a heatmap.
48
-
49
- >>> import pertpy as pt
50
- >>> mdata = pt.data.papalexi_2021()
51
- >>> gdo = mdata.mod['gdo']
52
- >>> ga = pt.pp.GuideAssignment()
53
- >>> ga.assign_by_threshold(gdo, assignment_threshold=5)
54
- >>> pt.pl.guide.heatmap(gdo)
55
- """
56
- data = adata.X if layer is None else adata.layers[layer]
57
-
58
- if order_by is None:
59
- max_guide_index = np.where(
60
- np.array(data.max(axis=1)).squeeze() != data.min(), np.array(data.argmax(axis=1)).squeeze(), -1
61
- )
62
- order = np.argsort(max_guide_index)
63
- elif isinstance(order_by, str):
64
- order = adata.obs[order_by]
65
- else:
66
- order = order_by
67
-
68
- adata.obs["_tmp_pertpy_grna_plot_dummy_group"] = ""
69
- if key_to_save_order is not None:
70
- adata.obs[key_to_save_order] = order
71
- axis_group = sc.pl.heatmap(
72
- adata[order],
73
- adata.var.index.tolist(),
74
- groupby="_tmp_pertpy_grna_plot_dummy_group",
75
- cmap="viridis",
76
- use_raw=False,
77
- dendrogram=False,
78
- layer=layer,
79
- **kwds,
80
- )
81
- del adata.obs["_tmp_pertpy_grna_plot_dummy_group"]
82
- return axis_group
pertpy/plot/_milopy.py DELETED
@@ -1,284 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- import matplotlib.pyplot as plt
6
- import numpy as np
7
- import pandas as pd
8
- import scanpy as sc
9
- import seaborn as sns
10
-
11
- if TYPE_CHECKING:
12
- from collections.abc import Sequence
13
-
14
- from mudata import MuData
15
-
16
-
17
- class MilopyPlot:
18
- """Plotting functions for Milopy."""
19
-
20
- @staticmethod
21
- def nhood_graph(
22
- mdata: MuData,
23
- alpha: float = 0.1,
24
- min_logFC: float = 0,
25
- min_size: int = 10,
26
- plot_edges: bool = False,
27
- title: str = "DA log-Fold Change",
28
- show: bool | None = None,
29
- save: bool | str | None = None,
30
- **kwargs,
31
- ) -> None:
32
- """Visualize DA results on abstracted graph (wrapper around sc.pl.embedding)
33
-
34
- Args:
35
- mdata: MuData object
36
- alpha: Significance threshold. (default: 0.1)
37
- min_logFC: Minimum absolute log-Fold Change to show results. If is 0, show all significant neighbourhoods. (default: 0)
38
- min_size: Minimum size of nodes in visualization. (default: 10)
39
- plot_edges: If edges for neighbourhood overlaps whould be plotted. Defaults to False.
40
- title: Plot title. Defaults to "DA log-Fold Change".
41
- show: Show the plot, do not return axis.
42
- save: If `True` or a `str`, save the figure. A string is appended to the default filename.
43
- Infer the filetype if ending on {`'.pdf'`, `'.png'`, `'.svg'`}.
44
- **kwargs: Additional arguments to `scanpy.pl.embedding`.
45
-
46
- Examples:
47
- >>> import pertpy as pt
48
- >>> adata = pt.dt.bhattacherjee()
49
- >>> milo = pt.tl.Milo()
50
- >>> mdata = milo.load(adata)
51
- >>> sc.pp.neighbors(mdata["rna"])
52
- >>> sc.tl.umap(mdata["rna"])
53
- >>> milo.make_nhoods(mdata["rna"])
54
- >>> mdata = milo.count_nhoods(mdata, sample_col="orig.ident")
55
- >>> milo.da_nhoods(mdata, design="~label")
56
- >>> milo.build_nhood_graph(mdata)
57
- >>> pt.pl.milo.nhood_graph(mdata)
58
- # TODO: If necessary adjust after fixing StopIteration error, which is currently thrown
59
- """
60
- nhood_adata = mdata["milo"].T.copy()
61
-
62
- if "Nhood_size" not in nhood_adata.obs.columns:
63
- raise KeyError(
64
- 'Cannot find "Nhood_size" column in adata.uns["nhood_adata"].obs -- \
65
- please run milopy.utils.build_nhood_graph(adata)'
66
- )
67
-
68
- nhood_adata.obs["graph_color"] = nhood_adata.obs["logFC"]
69
- nhood_adata.obs.loc[nhood_adata.obs["SpatialFDR"] > alpha, "graph_color"] = np.nan
70
- nhood_adata.obs["abs_logFC"] = abs(nhood_adata.obs["logFC"])
71
- nhood_adata.obs.loc[nhood_adata.obs["abs_logFC"] < min_logFC, "graph_color"] = np.nan
72
-
73
- # Plotting order - extreme logFC on top
74
- nhood_adata.obs.loc[nhood_adata.obs["graph_color"].isna(), "abs_logFC"] = np.nan
75
- ordered = nhood_adata.obs.sort_values("abs_logFC", na_position="first").index
76
- nhood_adata = nhood_adata[ordered]
77
-
78
- vmax = np.max([nhood_adata.obs["graph_color"].max(), abs(nhood_adata.obs["graph_color"].min())])
79
- vmin = -vmax
80
-
81
- sc.pl.embedding(
82
- nhood_adata,
83
- "X_milo_graph",
84
- color="graph_color",
85
- cmap="RdBu_r",
86
- size=nhood_adata.obs["Nhood_size"] * min_size,
87
- edges=plot_edges,
88
- neighbors_key="nhood",
89
- sort_order=False,
90
- frameon=False,
91
- vmax=vmax,
92
- vmin=vmin,
93
- title=title,
94
- show=show,
95
- save=save,
96
- **kwargs,
97
- )
98
-
99
- @staticmethod
100
- def nhood(
101
- mdata: MuData,
102
- ix: int,
103
- feature_key: str | None = "rna",
104
- basis="X_umap",
105
- show: bool | None = None,
106
- save: bool | str | None = None,
107
- **kwargs,
108
- ) -> None:
109
- """Visualize cells in a neighbourhood.
110
-
111
- Args:
112
- mdata: MuData object with feature_key slot, storing neighbourhood assignments in `mdata[feature_key].obsm['nhoods']`
113
- ix: index of neighbourhood to visualize
114
- basis: Embedding to use for visualization. Defaults to "X_umap".
115
- show: Show the plot, do not return axis.
116
- save: If True or a str, save the figure. A string is appended to the default filename. Infer the filetype if ending on {'.pdf', '.png', '.svg'}.
117
- **kwargs: Additional arguments to `scanpy.pl.embedding`.
118
-
119
- Examples:
120
- >>> import pertpy as pt
121
- >>> import scanpy as sc
122
- >>> adata = pt.dt.bhattacherjee()
123
- >>> milo = pt.tl.Milo()
124
- >>> mdata = milo.load(adata)
125
- >>> sc.pp.neighbors(mdata["rna"])
126
- >>> sc.tl.umap(mdata["rna"])
127
- >>> milo.make_nhoods(mdata["rna"])
128
- >>> pt.pl.milo.nhood(mdata, ix=0)
129
- """
130
-
131
- mdata[feature_key].obs["Nhood"] = mdata[feature_key].obsm["nhoods"][:, ix].toarray().ravel()
132
- sc.pl.embedding(
133
- mdata[feature_key], basis, color="Nhood", size=30, title="Nhood" + str(ix), show=show, save=save, **kwargs
134
- )
135
-
136
- @staticmethod
137
- def da_beeswarm(
138
- mdata: MuData,
139
- feature_key: str | None = "rna",
140
- anno_col: str = "nhood_annotation",
141
- alpha: float = 0.1,
142
- subset_nhoods: list[str] = None,
143
- palette: str | Sequence[str] | dict[str, str] | None = None,
144
- ):
145
- """Plot beeswarm plot of logFC against nhood labels
146
-
147
- Args:
148
- mdata: MuData object
149
- anno_col: Column in adata.uns['nhood_adata'].obs to use as annotation. (default: 'nhood_annotation'.)
150
- alpha: Significance threshold. (default: 0.1)
151
- subset_nhoods: List of nhoods to plot. If None, plot all nhoods. (default: None)
152
- palette: Name of Seaborn color palette for violinplots.
153
- Defaults to pre-defined category colors for violinplots.
154
-
155
- Examples:
156
- >>> import pertpy as pt
157
- >>> import scanpy as sc
158
- >>> adata = pt.dt.bhattacherjee()
159
- >>> milo = pt.tl.Milo()
160
- >>> mdata = milo.load(adata)
161
- >>> sc.pp.neighbors(mdata["rna"])
162
- >>> milo.make_nhoods(mdata["rna"])
163
- >>> mdata = milo.count_nhoods(mdata, sample_col="orig.ident")
164
- >>> milo.da_nhoods(mdata, design="~label")
165
- >>> milo.annotate_nhoods(mdata, anno_col='cell_type')
166
- >>> pt.pl.milo.da_beeswarm(mdata)
167
- """
168
- try:
169
- nhood_adata = mdata["milo"].T.copy()
170
- except KeyError:
171
- raise RuntimeError(
172
- "mdata should be a MuData object with two slots: feature_key and 'milo'. Run 'milopy.count_nhoods(adata)' first."
173
- ) from None
174
-
175
- if subset_nhoods is not None:
176
- nhood_adata = nhood_adata[subset_nhoods]
177
-
178
- try:
179
- nhood_adata.obs[anno_col]
180
- except KeyError:
181
- raise RuntimeError(
182
- f"Unable to find {anno_col} in mdata.uns['nhood_adata']. Run 'milopy.utils.annotate_nhoods(adata, anno_col)' first"
183
- ) from None
184
-
185
- try:
186
- nhood_adata.obs["logFC"]
187
- except KeyError:
188
- raise RuntimeError(
189
- "Unable to find 'logFC' in mdata.uns['nhood_adata'].obs. Run 'core.da_nhoods(adata)' first."
190
- ) from None
191
-
192
- sorted_annos = (
193
- nhood_adata.obs[[anno_col, "logFC"]].groupby(anno_col).median().sort_values("logFC", ascending=True).index
194
- )
195
-
196
- anno_df = nhood_adata.obs[[anno_col, "logFC", "SpatialFDR"]].copy()
197
- anno_df["is_signif"] = anno_df["SpatialFDR"] < alpha
198
- anno_df = anno_df[anno_df[anno_col] != "nan"]
199
-
200
- try:
201
- obs_col = nhood_adata.uns["annotation_obs"]
202
- if palette is None:
203
- palette = dict(
204
- zip(mdata[feature_key].obs[obs_col].cat.categories, mdata[feature_key].uns[f"{obs_col}_colors"])
205
- )
206
- sns.violinplot(
207
- data=anno_df,
208
- y=anno_col,
209
- x="logFC",
210
- order=sorted_annos,
211
- size=190,
212
- inner=None,
213
- orient="h",
214
- palette=palette,
215
- linewidth=0,
216
- scale="width",
217
- )
218
- except BaseException: # noqa: BLE001
219
- sns.violinplot(
220
- data=anno_df,
221
- y=anno_col,
222
- x="logFC",
223
- order=sorted_annos,
224
- size=190,
225
- inner=None,
226
- orient="h",
227
- linewidth=0,
228
- scale="width",
229
- )
230
- sns.stripplot(
231
- data=anno_df,
232
- y=anno_col,
233
- x="logFC",
234
- order=sorted_annos,
235
- size=2,
236
- hue="is_signif",
237
- palette=["grey", "black"],
238
- orient="h",
239
- alpha=0.5,
240
- )
241
- plt.legend(loc="upper left", title=f"< {int(alpha * 100)}% SpatialFDR", bbox_to_anchor=(1, 1), frameon=False)
242
- plt.axvline(x=0, ymin=0, ymax=1, color="black", linestyle="--")
243
-
244
- @staticmethod
245
- def nhood_counts_by_cond(
246
- mdata: MuData,
247
- test_var: str,
248
- subset_nhoods: list = None,
249
- log_counts: bool = False,
250
- ):
251
- """Plot boxplot of cell numbers vs condition of interest
252
-
253
- Args:
254
- mdata: MuData object storing cell level and nhood level information
255
- test_var: Name of column in adata.obs storing condition of interest (y-axis for boxplot)
256
- subset_nhoods: List of obs_names for neighbourhoods to include in plot. If None, plot all nhoods. (default: None)
257
- log_counts: Whether to plot log1p of cell counts. (default: False)
258
- """
259
- try:
260
- nhood_adata = mdata["milo"].T.copy()
261
- except KeyError:
262
- raise RuntimeError(
263
- "mdata should be a MuData object with two slots: feature_key and 'milo'. Run milopy.count_nhoods(mdata) first"
264
- ) from None
265
-
266
- if subset_nhoods is None:
267
- subset_nhoods = nhood_adata.obs_names
268
-
269
- pl_df = pd.DataFrame(nhood_adata[subset_nhoods].X.A, columns=nhood_adata.var_names).melt(
270
- var_name=nhood_adata.uns["sample_col"], value_name="n_cells"
271
- )
272
- pl_df = pd.merge(pl_df, nhood_adata.var)
273
- pl_df["log_n_cells"] = np.log1p(pl_df["n_cells"])
274
- if not log_counts:
275
- sns.boxplot(data=pl_df, x=test_var, y="n_cells", color="lightblue")
276
- sns.stripplot(data=pl_df, x=test_var, y="n_cells", color="black", s=3)
277
- plt.ylabel("# cells")
278
- else:
279
- sns.boxplot(data=pl_df, x=test_var, y="log_n_cells", color="lightblue")
280
- sns.stripplot(data=pl_df, x=test_var, y="log_n_cells", color="black", s=3)
281
- plt.ylabel("log(# cells + 1)")
282
-
283
- plt.xticks(rotation=90)
284
- plt.xlabel(test_var)