upsetplot-bombcell 0.10.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.
@@ -0,0 +1,49 @@
1
+ """
2
+ ================================================
3
+ Design: Customizing element size and figure size
4
+ ================================================
5
+
6
+ This example illustrates controlling sizing within an UpSet plot.
7
+ """
8
+
9
+ from matplotlib import pyplot as plt
10
+
11
+ from upsetplot import generate_counts, plot
12
+
13
+ example = generate_counts()
14
+ print(example)
15
+
16
+ plot(example)
17
+ plt.suptitle("Defaults")
18
+ plt.show()
19
+
20
+ ##########################################################################
21
+ # upsetplot uses a grid of square "elements" to display. Controlling the
22
+ # size of these elements affects all components of the plot.
23
+
24
+ plot(example, element_size=40)
25
+ plt.suptitle("Increased element_size")
26
+ plt.show()
27
+
28
+ ##########################################################################
29
+ # When setting ``figsize`` explicitly, you then need to pass the figure to
30
+ # ``plot``, and use ``element_size=None`` for optimal sizing.
31
+
32
+ fig = plt.figure(figsize=(10, 3))
33
+ plot(example, fig=fig, element_size=None)
34
+ plt.suptitle("Setting figsize explicitly")
35
+ plt.show()
36
+
37
+ ##########################################################################
38
+ # Components in the plot can be resized by indicating how many elements
39
+ # they should equate to.
40
+
41
+ plot(example, intersection_plot_elements=3)
42
+ plt.suptitle("Decreased intersection_plot_elements")
43
+ plt.show()
44
+
45
+ ##########################################################################
46
+
47
+ plot(example, totals_plot_elements=5)
48
+ plt.suptitle("Increased totals_plot_elements")
49
+ plt.show()
@@ -0,0 +1,82 @@
1
+ """
2
+ ============================
3
+ Design: Changing Plot Colors
4
+ ============================
5
+
6
+ This example illustrates use of matplotlib and upsetplot color settings, aside
7
+ from matplotlib style sheets, which can control colors as well as grid lines,
8
+ fonts and tick display.
9
+
10
+ Upsetplot provides some color settings:
11
+
12
+ * ``facecolor``: sets the color for intersection size bars, and for active
13
+ matrix dots. Defaults to white on a dark background, otherwise black.
14
+ * ``other_dots_color``: sets the color for other (inactive) dots. Specify as a
15
+ color, or a float specifying opacity relative to facecolor.
16
+ * ``shading_color``: sets the color odd rows. Specify as a color, or a float
17
+ specifying opacity relative to facecolor.
18
+
19
+ For an introduction to matplotlib theming see:
20
+
21
+ * `Tutorial
22
+ <https://matplotlib.org/stable/tutorials/introductory/customizing.html>`__
23
+ * `Reference
24
+ <https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html>`__
25
+ """
26
+
27
+ from matplotlib import pyplot as plt
28
+
29
+ from upsetplot import generate_counts, plot
30
+
31
+ example = generate_counts()
32
+
33
+ plot(example, facecolor="darkblue")
34
+ plt.suptitle('facecolor="darkblue"')
35
+ plt.show()
36
+
37
+ ##########################################################################
38
+
39
+ plot(example, facecolor="darkblue", shading_color="lightgray")
40
+ plt.suptitle('facecolor="darkblue", shading_color="lightgray"')
41
+ plt.show()
42
+
43
+ ##########################################################################
44
+
45
+ with plt.style.context("Solarize_Light2"):
46
+ plot(example)
47
+ plt.suptitle("matplotlib classic stylesheet")
48
+ plt.show()
49
+
50
+ ##########################################################################
51
+
52
+ with plt.style.context("dark_background"):
53
+ plot(example, show_counts=True)
54
+ plt.suptitle("matplotlib dark_background stylesheet")
55
+ plt.show()
56
+
57
+ ##########################################################################
58
+
59
+ with plt.style.context("dark_background"):
60
+ plot(example, show_counts=True, shading_color=0.15)
61
+ plt.suptitle("matplotlib dark_background stylesheet, shading_color=.15")
62
+ plt.show()
63
+
64
+ ##########################################################################
65
+
66
+ with plt.style.context("dark_background"):
67
+ plot(example, show_counts=True, facecolor="red")
68
+ plt.suptitle('matplotlib dark_background, facecolor="red"')
69
+ plt.show()
70
+
71
+ ##########################################################################
72
+
73
+ with plt.style.context("dark_background"):
74
+ plot(
75
+ example,
76
+ show_counts=True,
77
+ facecolor="red",
78
+ other_dots_color=0.4,
79
+ shading_color=0.2,
80
+ )
81
+ plt.suptitle("dark_background, red face, stronger other colors")
82
+ plt.show()
@@ -0,0 +1,28 @@
1
+ """
2
+ ===========================
3
+ Basic: Vertical orientation
4
+ ===========================
5
+
6
+ This illustrates the effect of orientation='vertical'.
7
+ """
8
+
9
+ from matplotlib import pyplot as plt
10
+
11
+ from upsetplot import generate_counts, plot
12
+
13
+ example = generate_counts()
14
+ plot(example, orientation="vertical")
15
+ plt.suptitle("A vertical plot")
16
+ plt.show()
17
+
18
+ ##########################################################################
19
+
20
+ plot(example, orientation="vertical", show_counts="{:d}")
21
+ plt.suptitle("A vertical plot with counts shown")
22
+ plt.show()
23
+
24
+ ##########################################################################
25
+
26
+ plot(example, orientation="vertical", show_counts="{:d}", show_percentages=True)
27
+ plt.suptitle("With counts and percentages shown")
28
+ plt.show()
upsetplot/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ __version__ = "0.10.0"
2
+
3
+ from .data import (
4
+ from_contents,
5
+ from_indicators,
6
+ from_memberships,
7
+ generate_counts,
8
+ generate_data,
9
+ generate_samples,
10
+ )
11
+ from .plotting import UpSet, plot
12
+ from .reformat import query
13
+
14
+ __all__ = [
15
+ "UpSet",
16
+ "generate_data",
17
+ "generate_counts",
18
+ "generate_samples",
19
+ "plot",
20
+ "from_memberships",
21
+ "from_contents",
22
+ "from_indicators",
23
+ "query",
24
+ ]
upsetplot/data.py ADDED
@@ -0,0 +1,420 @@
1
+ import warnings
2
+ from numbers import Number
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+
8
+ def generate_samples(seed=0, n_samples=10000, n_categories=3):
9
+ """Generate artificial samples assigned to set intersections
10
+
11
+ Parameters
12
+ ----------
13
+ seed : int
14
+ A seed for randomisation
15
+ n_samples : int
16
+ Number of samples to generate
17
+ n_categories : int
18
+ Number of categories (named "cat0", "cat1", ...) to generate
19
+
20
+ Returns
21
+ -------
22
+ DataFrame
23
+ Field 'value' is a weight or score for each element.
24
+ Field 'index' is a unique id for each element.
25
+ Index includes a boolean indicator mask for each category.
26
+
27
+ Note: Further fields may be added in future versions.
28
+
29
+ See Also
30
+ --------
31
+ generate_counts : Generates the counts for each subset of categories
32
+ corresponding to these samples.
33
+ """
34
+ rng = np.random.RandomState(seed)
35
+ df = pd.DataFrame({"value": np.zeros(n_samples)})
36
+ for i in range(n_categories):
37
+ r = rng.rand(n_samples)
38
+ df["cat%d" % i] = r > rng.rand()
39
+ df["value"] += r
40
+
41
+ df.reset_index(inplace=True)
42
+ df.set_index(["cat%d" % i for i in range(n_categories)], inplace=True)
43
+ return df
44
+
45
+
46
+ def generate_counts(seed=0, n_samples=10000, n_categories=3):
47
+ """Generate artificial counts corresponding to set intersections
48
+
49
+ Parameters
50
+ ----------
51
+ seed : int
52
+ A seed for randomisation
53
+ n_samples : int
54
+ Number of samples to generate statistics over
55
+ n_categories : int
56
+ Number of categories (named "cat0", "cat1", ...) to generate
57
+
58
+ Returns
59
+ -------
60
+ Series
61
+ Counts indexed by boolean indicator mask for each category.
62
+
63
+ See Also
64
+ --------
65
+ generate_samples : Generates a DataFrame of samples that these counts are
66
+ derived from.
67
+ """
68
+ df = generate_samples(seed=seed, n_samples=n_samples, n_categories=n_categories)
69
+ return df.value.groupby(level=list(range(n_categories))).count()
70
+
71
+
72
+ def generate_data(seed=0, n_samples=10000, n_sets=3, aggregated=False):
73
+ warnings.warn(
74
+ "generate_data was replaced by generate_counts in version "
75
+ "0.3 and will be removed in version 0.4.",
76
+ DeprecationWarning,
77
+ stacklevel=2,
78
+ )
79
+ if aggregated:
80
+ return generate_counts(seed=seed, n_samples=n_samples, n_categories=n_sets)
81
+ else:
82
+ return generate_samples(seed=seed, n_samples=n_samples, n_categories=n_sets)[
83
+ "value"
84
+ ]
85
+
86
+
87
+ def from_indicators(indicators, data=None):
88
+ """Load category membership indicated by a boolean indicator matrix
89
+
90
+ This loader also supports the case where the indicator columns can be
91
+ derived from `data`.
92
+
93
+ .. versionadded:: 0.6
94
+
95
+ Parameters
96
+ ----------
97
+ indicators : DataFrame-like of booleans, Sequence of str, or callable
98
+ Specifies the category indicators (boolean mask arrays) within
99
+ ``data``, i.e. which records in ``data`` belong to which categories.
100
+
101
+ If a list of strings, these should be column names found in ``data``
102
+ whose values are boolean mask arrays.
103
+
104
+ If a DataFrame, its columns should correspond to categories, and its
105
+ index should be a subset of those in ``data``, values should be True
106
+ where a data record is in that category, and False or NA otherwise.
107
+
108
+ If callable, it will be applied to ``data`` after the latter is
109
+ converted to a Series or DataFrame.
110
+
111
+ data : Series-like or DataFrame-like, optional
112
+ If given, the index of category membership is attached to this data.
113
+ It must have the same length as `indicators`.
114
+ If not given, the series will contain the value 1.
115
+
116
+ Returns
117
+ -------
118
+ DataFrame or Series
119
+ `data` is returned with its index indicating category membership.
120
+ It will be a Series if `data` is a Series or 1d numeric array or None.
121
+
122
+ Notes
123
+ -----
124
+ Categories with indicators that are all False will be removed.
125
+
126
+ Examples
127
+ --------
128
+ >>> import pandas as pd
129
+ >>> from upsetplot import from_indicators
130
+ >>>
131
+ >>> # Just indicators:
132
+ >>> indicators = {"cat1": [True, False, True, False],
133
+ ... "cat2": [False, True, False, False],
134
+ ... "cat3": [True, True, False, False]}
135
+ >>> from_indicators(indicators)
136
+ cat1 cat2 cat3
137
+ True False True 1.0
138
+ False True True 1.0
139
+ True False False 1.0
140
+ False False False 1.0
141
+ Name: ones, dtype: float64
142
+ >>>
143
+ >>> # Where indicators are included within data, specifying
144
+ >>> # columns by name:
145
+ >>> data = pd.DataFrame({"value": [5, 4, 6, 4], **indicators})
146
+ >>> from_indicators(["cat1", "cat3"], data=data)
147
+ value cat1 cat2 cat3
148
+ cat1 cat3
149
+ True True 5 True False True
150
+ False True 4 False True True
151
+ True False 6 True False False
152
+ False False 4 False False False
153
+ >>>
154
+ >>> # Making indicators out of all boolean columns:
155
+ >>> from_indicators(lambda data: data.select_dtypes(bool), data=data)
156
+ value cat1 cat2 cat3
157
+ cat1 cat2 cat3
158
+ True False True 5 True False True
159
+ False True True 4 False True True
160
+ True False False 6 True False False
161
+ False False False 4 False False False
162
+ >>>
163
+ >>> # Using a dataset with missing data, we can use missingness as
164
+ >>> # an indicator:
165
+ >>> data = pd.DataFrame({"val1": [pd.NA, .7, pd.NA, .9],
166
+ ... "val2": ["male", pd.NA, "female", "female"],
167
+ ... "val3": [pd.NA, pd.NA, 23000, 78000]})
168
+ >>> from_indicators(pd.isna, data=data)
169
+ val1 val2 val3
170
+ val1 val2 val3
171
+ True False True <NA> male <NA>
172
+ False True True 0.7 <NA> <NA>
173
+ True False False <NA> female 23000
174
+ False False False 0.9 female 78000
175
+ """
176
+ if data is not None:
177
+ data = _convert_to_pandas(data)
178
+
179
+ if callable(indicators):
180
+ if data is None:
181
+ raise ValueError("data must be provided when indicators is " "callable")
182
+ indicators = indicators(data)
183
+
184
+ try:
185
+ indicators[0]
186
+ except Exception:
187
+ pass
188
+ else:
189
+ if isinstance(indicators[0], (str, int)):
190
+ if data is None:
191
+ raise ValueError(
192
+ "data must be provided when indicators are "
193
+ "specified as a list of columns"
194
+ )
195
+ if isinstance(indicators, tuple):
196
+ raise ValueError("indicators as tuple is not supported")
197
+ # column array
198
+ indicators = data[indicators]
199
+
200
+ indicators = pd.DataFrame(indicators)
201
+ # Fill NaN with False, using where() to avoid pandas FutureWarning about downcasting
202
+ indicators = indicators.where(pd.notna(indicators), False).infer_objects(copy=False)
203
+ # drop all-False (should we be dropping all-True also? making an option?)
204
+ indicators = indicators.loc[:, indicators.any(axis=0)]
205
+
206
+ if not all(dtype.kind == "b" for dtype in indicators.dtypes):
207
+ raise ValueError("The indicators must all be boolean")
208
+
209
+ if data is not None:
210
+ if not (
211
+ isinstance(indicators.index, pd.RangeIndex)
212
+ and indicators.index[0] == 0
213
+ and indicators.index[-1] == len(data) - 1
214
+ ):
215
+ # index is specified on indicators. Need to align it to data
216
+ if not indicators.index.isin(data.index).all():
217
+ raise ValueError(
218
+ "If indicators.index is not the default, "
219
+ "all its values must be present in "
220
+ "data.index"
221
+ )
222
+ indicators = indicators.reindex(index=data.index, fill_value=False)
223
+ else:
224
+ data = pd.Series(np.ones(len(indicators)), name="ones")
225
+
226
+ indicators.set_index(list(indicators.columns), inplace=True)
227
+ # Ensure we always have a MultiIndex, even with a single category
228
+ if not isinstance(indicators.index, pd.MultiIndex):
229
+ indicators.index = pd.MultiIndex.from_arrays(
230
+ [indicators.index], names=[indicators.index.name]
231
+ )
232
+ data.index = indicators.index
233
+
234
+ return data
235
+
236
+
237
+ def _convert_to_pandas(data, copy=True):
238
+ is_series = False
239
+ if hasattr(data, "loc"):
240
+ if copy:
241
+ data = data.copy(deep=False)
242
+ is_series = data.ndim == 1
243
+ elif len(data):
244
+ try:
245
+ is_series = isinstance(data[0], Number)
246
+ except KeyError:
247
+ is_series = False
248
+ return pd.Series(data) if is_series else pd.DataFrame(data)
249
+
250
+
251
+ def from_memberships(memberships, data=None):
252
+ """Load data where each sample has a collection of category names
253
+
254
+ The output should be suitable for passing to `UpSet` or `plot`.
255
+
256
+ Parameters
257
+ ----------
258
+ memberships : sequence of collections of strings
259
+ Each element corresponds to a data point, indicating the sets it is a
260
+ member of. Each category is named by a string.
261
+ data : Series-like or DataFrame-like, optional
262
+ If given, the index of category memberships is attached to this data.
263
+ It must have the same length as `memberships`.
264
+ If not given, the series will contain the value 1.
265
+
266
+ Returns
267
+ -------
268
+ DataFrame or Series
269
+ `data` is returned with its index indicating category membership.
270
+ It will be a Series if `data` is a Series or 1d numeric array.
271
+ The index will have levels ordered by category names.
272
+
273
+ Examples
274
+ --------
275
+ >>> from upsetplot import from_memberships
276
+ >>> from_memberships([
277
+ ... ['cat1', 'cat3'],
278
+ ... ['cat2', 'cat3'],
279
+ ... ['cat1'],
280
+ ... []
281
+ ... ])
282
+ cat1 cat2 cat3
283
+ True False True 1
284
+ False True True 1
285
+ True False False 1
286
+ False False False 1
287
+ Name: ones, dtype: ...
288
+ >>> # now with data:
289
+ >>> import numpy as np
290
+ >>> from_memberships([
291
+ ... ['cat1', 'cat3'],
292
+ ... ['cat2', 'cat3'],
293
+ ... ['cat1'],
294
+ ... []
295
+ ... ], data=np.arange(12).reshape(4, 3))
296
+ 0 1 2
297
+ cat1 cat2 cat3
298
+ True False True 0 1 2
299
+ False True True 3 4 5
300
+ True False False 6 7 8
301
+ False False False 9 10 11
302
+ """
303
+ df = pd.DataFrame([{name: True for name in names} for names in memberships])
304
+ for set_name in df.columns:
305
+ if not hasattr(set_name, "lower"):
306
+ raise ValueError("Category names should be strings")
307
+ if df.shape[1] == 0:
308
+ raise ValueError("Require at least one category. None were found.")
309
+ df.sort_index(axis=1, inplace=True)
310
+ # Convert to bool, treating NaN as False (avoids pandas FutureWarning)
311
+ df = df.eq(True)
312
+ df.set_index(list(df.columns), inplace=True)
313
+ # Ensure we always have a MultiIndex, even with a single category
314
+ if not isinstance(df.index, pd.MultiIndex):
315
+ df.index = pd.MultiIndex.from_arrays([df.index], names=[df.index.name])
316
+ if data is None:
317
+ return df.assign(ones=1)["ones"]
318
+
319
+ data = _convert_to_pandas(data)
320
+ if len(data) != len(df):
321
+ raise ValueError(
322
+ "memberships and data must have the same length. "
323
+ "Got len(memberships) == %d, len(data) == %d"
324
+ % (len(memberships), len(data))
325
+ )
326
+ data.index = df.index
327
+ return data
328
+
329
+
330
+ def from_contents(contents, data=None, id_column="id"):
331
+ """Build data from category listings
332
+
333
+ Parameters
334
+ ----------
335
+ contents : Mapping (or iterable over pairs) of strings to sets
336
+ Keys are category names, values are sets of identifiers (int or
337
+ string).
338
+ data : DataFrame, optional
339
+ If provided, this should be indexed by the identifiers used in
340
+ `contents`.
341
+ id_column : str, default='id'
342
+ The column name to use for the identifiers in the output.
343
+
344
+ Returns
345
+ -------
346
+ DataFrame
347
+ `data` is returned with its index indicating category membership,
348
+ including a column named according to id_column.
349
+ If data is not given, the order of rows is not assured.
350
+
351
+ Notes
352
+ -----
353
+ The order of categories in the output DataFrame is determined from
354
+ `contents`, which may have non-deterministic iteration order.
355
+
356
+ Examples
357
+ --------
358
+ >>> from upsetplot import from_contents
359
+ >>> contents = {'cat1': ['a', 'b', 'c'],
360
+ ... 'cat2': ['b', 'd'],
361
+ ... 'cat3': ['e']}
362
+ >>> from_contents(contents)
363
+ id
364
+ cat1 cat2 cat3
365
+ True False False a
366
+ True False b
367
+ False False c
368
+ False True False d
369
+ False True e
370
+ >>> import pandas as pd
371
+ >>> contents = {'cat1': [0, 1, 2],
372
+ ... 'cat2': [1, 3],
373
+ ... 'cat3': [4]}
374
+ >>> data = pd.DataFrame({'favourite': ['green', 'red', 'red',
375
+ ... 'yellow', 'blue']})
376
+ >>> from_contents(contents, data=data)
377
+ id favourite
378
+ cat1 cat2 cat3
379
+ True False False 0 green
380
+ True False 1 red
381
+ False False 2 red
382
+ False True False 3 yellow
383
+ False True 4 blue
384
+ """
385
+ cat_series = [
386
+ pd.Series(True, index=list(elements), name=name)
387
+ for name, elements in contents.items()
388
+ ]
389
+ if not all(s.index.is_unique for s in cat_series):
390
+ raise ValueError("Got duplicate ids in a category")
391
+
392
+ df = pd.concat(cat_series, axis=1, sort=False)
393
+ if id_column in df.columns:
394
+ raise ValueError("A category cannot be named %r" % id_column)
395
+ # Convert to bool, treating NaN as False (avoids pandas FutureWarning)
396
+ df = df.eq(True)
397
+ cat_names = list(df.columns)
398
+
399
+ if data is not None:
400
+ if set(df.columns).intersection(data.columns):
401
+ raise ValueError("Data columns overlap with category names")
402
+ if id_column in data.columns:
403
+ raise ValueError("data cannot contain a column named %r" % id_column)
404
+ not_in_data = df.drop(data.index, axis=0, errors="ignore")
405
+ if len(not_in_data):
406
+ raise ValueError(
407
+ "Found identifiers in contents that are not in "
408
+ "data: %r" % not_in_data.index.values
409
+ )
410
+ # Use eq(True) to convert NaN to False without FutureWarning
411
+ df = df.reindex(index=data.index).eq(True)
412
+ df = pd.concat([data, df], axis=1, sort=False)
413
+ df.index.name = id_column
414
+ result = df.reset_index().set_index(cat_names)
415
+ # Ensure we always have a MultiIndex, even with a single category
416
+ if not isinstance(result.index, pd.MultiIndex):
417
+ result.index = pd.MultiIndex.from_arrays(
418
+ [result.index], names=[result.index.name]
419
+ )
420
+ return result