Skip to content

Commit efc0732

Browse files
authored
Merge pull request #77 from ACCLAB/v0.2.6-dev
v0.2.6
2 parents d19e139 + 36f573e commit efc0732

File tree

81 files changed

+671
-366
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+671
-366
lines changed

.travis.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ install:
2222
- conda config --add channels conda-forge
2323
- conda create -n testenv --yes pip python=$PYTHON matplotlib
2424
- source activate testenv
25-
- pip install pytest==4.3
26-
- pip install .
25+
- pip install .[dev]
2726

2827

2928
script: pytest dabest

CONTRIBUTING.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99

1010
## Did you write a patch that fixes a bug?
11-
- Open a new GitHub [pull request](https://help.github.com/en/articles/about-pull-requests)(PR for short) with the patch.
11+
- Open a new GitHub [pull request](https://help.github.com/en/articles/about-pull-requests) (PR for short) with the patch.
1212

13-
- Create the PR into the development branch, which is indicated by `v{latest version number}_dev`.
13+
- Create the PR into the development branch, which is indicated by `v{latest version number}-dev`.
1414

1515
- Clearly state the problem and solution in the PR description. Include the relevant [issue number](https://guides.github.com/features/issues/) if applicable.
1616

README.md

+6-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[![Free-to-view citation](https://zenodo.org/badge/DOI/10.1038/s41592-019-0470-3.svg)](https://rdcu.be/bHhJ4)
77
[![License](https://img.shields.io/badge/License-BSD%203--Clause--Clear-orange.svg)](https://spdx.org/licenses/BSD-3-Clause-Clear.html)
88

9+
910
## About
1011

1112
DABEST is a package for **D**ata **A**nalysis using **B**ootstrap-Coupled **EST**imation.
@@ -22,21 +23,12 @@ An estimation plot has two key features.
2223

2324
DABEST powers [estimationstats.com](https://www.estimationstats.com/), allowing everyone access to high-quality estimation plots.
2425

25-
## Requirements
26-
27-
DABEST has been tested on Python 3.5, 3.6, and 3.7.
28-
29-
In addition, the following packages are also required:
30-
- [numpy](https://www.numpy.org) (1.15)
31-
- [scipy](https://www.scipy.org) (1.2)
32-
- [matplotlib](https://www.matplotlib.org) (3.0)
33-
- [seaborn](https://seaborn.pydata.org) (0.9)
34-
- [pandas](https://pandas.pydata.org) (0.24).
35-
36-
To obtain these package dependencies easily, it is highly recommended to download the [Anaconda distribution](https://www.continuum.io/downloads) of Python.
3726

3827
## Installation
3928

29+
This package is tested on Python 3.5, 3.6, and 3.7.
30+
It is highly recommended to download the [Anaconda distribution](https://www.continuum.io/downloads) of Python in order to obtain the dependencies easily.
31+
4032
You can install this package via `pip`.
4133

4234
To install, at the command line run
@@ -56,6 +48,7 @@ Then, navigate to the cloned repo in the command line and run
5648
pip install .
5749
```
5850

51+
5952
## Usage
6053

6154
```python3
@@ -99,6 +92,7 @@ All contributions are welcome; please read the [Guidelines for contributing](htt
9992

10093
We also have a [Code of Conduct](https://github.com/ACCLAB/DABEST-python/blob/master/CODE_OF_CONDUCT.md) to foster an inclusive and productive space.
10194

95+
10296
## Acknowledgements
10397

10498
We would like to thank alpha testers from the [Claridge-Chang lab](https://www.claridgechang.net/): [Sangyu Xu](https://github.com/sangyu), [Xianyuan Zhang](https://github.com/XYZfar), [Farhan Mohammad](https://github.com/farhan8igib), Jurga Mituzaitė, and Stanislav Ott.

dabest/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
from ._stats_tools import effsize as effsize
2424
from ._classes import TwoGroupsEffectSize
2525

26-
__version__ = "0.2.5"
26+
__version__ = "0.2.6"

dabest/_classes.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def __init__(self, control, test, effect_size,
460460
'statistic_wilcoxon': nan}
461461
"""
462462

463-
from numpy import array, isnan
463+
from numpy import array, isnan, isinf
464464
from numpy import sort as npsort
465465
from numpy.random import choice, seed
466466

@@ -522,6 +522,20 @@ def __init__(self, control, test, effect_size,
522522
control, test, is_paired, effect_size,
523523
resamples, random_seed)
524524
self.__bootstraps = npsort(bootstraps)
525+
526+
# Added in v0.2.6.
527+
# Raises a UserWarning if there are any infiinities in the bootstraps.
528+
num_infinities = len(self.__bootstraps[isinf(self.__bootstraps)])
529+
530+
if num_infinities > 0:
531+
warn_msg = "There are {} bootstrap(s) that are not defined. "\
532+
"This is likely due to smaple sample sizes. "\
533+
"The values in a bootstrap for a group will be more likely "\
534+
"to be all equal, with a resulting variance of zero. "\
535+
"The computation of Cohen's d and Hedges' g thus "\
536+
"involved a division by zero. "
537+
warnings.warn(warn_msg.format(num_infinities),
538+
category=UserWarning)
525539

526540
self.__bias_correction = ci2g.compute_meandiff_bias_correction(
527541
self.__bootstraps, self.__difference)
@@ -1103,6 +1117,7 @@ def plot(self, color_col=None,
11031117

11041118
fig_size=None,
11051119
dpi=100,
1120+
ax=None,
11061121

11071122
swarmplot_kwargs=None,
11081123
violinplot_kwargs=None,
@@ -1112,6 +1127,7 @@ def plot(self, color_col=None,
11121127
legend_kwargs=None):
11131128
"""
11141129
Creates an estimation plot for the effect size of interest.
1130+
11151131
11161132
Parameters
11171133
----------
@@ -1176,6 +1192,9 @@ def plot(self, color_col=None,
11761192
The desired dimensions of the figure as a (length, width) tuple.
11771193
dpi : int, default 100
11781194
The dots per inch of the resulting figure.
1195+
ax : matplotlib.Axes, default None
1196+
Provide an existing Axes for the plots to be created. If no Axes is
1197+
specified, a new matplotlib Figure will be created.
11791198
swarmplot_kwargs : dict, default None
11801199
Pass any keyword arguments accepted by the seaborn `swarmplot`
11811200
command here, as a dict. If None, the following keywords are
@@ -1206,9 +1225,14 @@ def plot(self, color_col=None,
12061225
12071226
Returns
12081227
-------
1209-
A :class:`matplotlib.figure.Figure` with 2 Axes.
1210-
1228+
A :class:`matplotlib.figure.Figure` with 2 Axes, if ``ax = None``.
1229+
12111230
The first axes (accessible with ``FigName.axes[0]``) contains the rawdata swarmplot; the second axes (accessible with ``FigName.axes[1]``) has the bootstrap distributions and effect sizes (with confidence intervals) plotted on it.
1231+
1232+
If ``ax`` is specified, the rawdata swarmplot is accessed at ``ax``
1233+
itself, while the effect size axes is accessed at ``ax.contrast_axes``.
1234+
See the last example below.
1235+
12121236
12131237
Examples
12141238
--------
@@ -1244,6 +1268,14 @@ def plot(self, color_col=None,
12441268
... "Test 2", "Test 3")
12451269
... )
12461270
>>> fig6 = my_shared_control.mean_diff.plot()
1271+
1272+
Creating estimation plots in individual panels of a figure.
1273+
1274+
>>> f, axx = plt.subplots(nrows=2, ncols=2, figsize=(15, 15))
1275+
>>> my_data.mean_diff.plot(ax=axx.flat[0])
1276+
>>> my_data_paired.mean_diff.plot(ax=axx.flat[1])
1277+
>>> my_shared_control.mean_diff.plot(ax=axx.flat[2])
1278+
>>> my_shared_control.mean_diff.plot(ax=axx.flat[3], float_contrast=False)
12471279
12481280
"""
12491281

@@ -1344,4 +1376,4 @@ def dabest_obj(self):
13441376
Returns the `dabest` object that invoked the current EffectSizeDataFrame
13451377
class.
13461378
"""
1347-
return self.__dabest_obj
1379+
return self.__dabest_obj

dabest/_stats_tools/confint_2group_diff.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,24 @@ def compute_bootstrapped_diff(x0, x1, is_paired, effect_size,
159159

160160
# reset seed
161161
np.random.seed()
162-
162+
163+
# check whether there are any infinities in the bootstrap,
164+
# which likely indicates the sample sizes are too small as
165+
# the computation of Cohen's d and Hedges' g necessitated
166+
# a division by zero.
167+
# Added in v0.2.6.
168+
169+
# num_infinities = len(out[np.isinf(out)])
170+
# print(num_infinities)
171+
# if num_infinities > 0:
172+
# warn_msg = "There are {} bootstraps that are not defined. "\
173+
# "This is likely due to smaple sample sizes. "\
174+
# "The values in a bootstrap for a group will be more likely "\
175+
# "to be all equal, with a resulting variance of zero. "\
176+
# "The computation of Cohen's d and Hedges' g will therefore "\
177+
# "involved a division by zero. "
178+
# warnings.warn(warn_msg.format(num_infinities), category="UserWarning")
179+
163180
return out
164181

165182

dabest/_stats_tools/effsize.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ def cohens_d(control, test, is_paired=False):
217217
# assume the two arrays are ordered already.
218218
delta = test - control
219219
M = np.mean(delta)
220-
return M / average_sd
220+
divisor = average_sd
221221

222222
else:
223223
M = np.mean(test) - np.mean(control)
224-
return M / pooled_sd
224+
divisor = pooled_sd
225+
226+
return M / divisor
225227

226228

227229

dabest/plotter.py

+74-24
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
3131
3232
fig_size=None,
3333
dpi=100,
34+
ax=None,
3435
3536
swarmplot_kwargs=None,
3637
violinplot_kwargs=None,
@@ -254,28 +255,76 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
254255

255256
# Initialise the figure.
256257
# sns.set(context="talk", style='ticks')
257-
init_fig_kwargs = dict(figsize=fig_size, dpi=plot_kwargs["dpi"])
258+
init_fig_kwargs = dict(figsize=fig_size, dpi=plot_kwargs["dpi"],
259+
tight_layout=True)
260+
261+
width_ratios_ga = [2.5, 1]
262+
h_space_cummings = 0.3
263+
if plot_kwargs["ax"] is not None:
264+
# New in v0.2.6.
265+
# Use inset axes to create the estimation plot inside a single axes.
266+
# Author: Adam L Nekimken. (PR #73)
267+
inset_contrast = True
268+
rawdata_axes = plot_kwargs["ax"]
269+
ax_position = rawdata_axes.get_position() # [[x0, y0], [x1, y1]]
270+
271+
fig = rawdata_axes.get_figure()
272+
273+
if float_contrast is True:
274+
axins = rawdata_axes.inset_axes(
275+
[1, 0,
276+
width_ratios_ga[1]/width_ratios_ga[0], 1])
277+
rawdata_axes.set_position( # [l, b, w, h]
278+
[ax_position.x0,
279+
ax_position.y0,
280+
(ax_position.x1 - ax_position.x0) * (width_ratios_ga[0] /
281+
sum(width_ratios_ga)),
282+
(ax_position.y1 - ax_position.y0)])
283+
284+
contrast_axes = axins
258285

259-
# Here, we hardcode some figure parameters.
260-
if float_contrast is True:
261-
fig, axx = plt.subplots(ncols=2,
262-
gridspec_kw={"width_ratios": [2.5, 1],
263-
"wspace": 0},
264-
**init_fig_kwargs)
286+
else:
287+
axins = rawdata_axes.inset_axes([0, -1 - h_space_cummings, 1, 1])
288+
plot_height = ((ax_position.y1 - ax_position.y0) /
289+
(2 + h_space_cummings))
290+
rawdata_axes.set_position(
291+
[ax_position.x0,
292+
ax_position.y0 + (1 + h_space_cummings) * plot_height,
293+
(ax_position.x1 - ax_position.x0),
294+
plot_height])
295+
296+
# If the contrast axes are NOT floating, create lists to store
297+
# raw ylims and raw tick intervals, so that I can normalize
298+
# their ylims later.
299+
contrast_ax_ylim_low = list()
300+
contrast_ax_ylim_high = list()
301+
contrast_ax_ylim_tickintervals = list()
302+
contrast_axes = axins
303+
rawdata_axes.contrast_axes = axins
265304

266305
else:
267-
fig, axx = plt.subplots(nrows=2,
268-
gridspec_kw={"hspace": 0.3},
269-
**init_fig_kwargs)
270-
271-
# If the contrast axes are NOT floating, create lists to store raw ylims
272-
# and raw tick intervals, so that I can normalize their ylims later.
273-
contrast_ax_ylim_low = list()
274-
contrast_ax_ylim_high = list()
275-
contrast_ax_ylim_tickintervals = list()
306+
inset_contrast = False
307+
# Here, we hardcode some figure parameters.
308+
if float_contrast is True:
309+
fig, axx = plt.subplots(
310+
ncols=2,
311+
gridspec_kw={"width_ratios": width_ratios_ga,
312+
"wspace": 0},
313+
**init_fig_kwargs)
276314

277-
rawdata_axes = axx[0]
278-
contrast_axes = axx[1]
315+
else:
316+
fig, axx = plt.subplots(nrows=2,
317+
gridspec_kw={"hspace": 0.3},
318+
**init_fig_kwargs)
319+
# If the contrast axes are NOT floating, create lists to store
320+
# raw ylims and raw tick intervals, so that I can normalize
321+
# their ylims later.
322+
contrast_ax_ylim_low = list()
323+
contrast_ax_ylim_high = list()
324+
contrast_ax_ylim_tickintervals = list()
325+
326+
rawdata_axes = axx[0]
327+
contrast_axes = axx[1]
279328

280329
rawdata_axes.set_frame_on(False)
281330
contrast_axes.set_frame_on(False)
@@ -423,7 +472,8 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
423472
current_ci_high = results.bca_high[j]
424473

425474
# Create the violinplot.
426-
v = contrast_axes.violinplot(current_bootstrap,
475+
# New in v0.2.6: drop negative infinities before plotting.
476+
v = contrast_axes.violinplot(current_bootstrap[~np.isinf(current_bootstrap)],
427477
positions=[tick],
428478
**violinplot_kwargs)
429479
# Turn the violinplot into half, and color it the same as the swarmplot.
@@ -651,19 +701,19 @@ def EffectSizeDataFramePlotter(EffectSizeDataFrame, **plot_kwargs):
651701

652702
# Compute the end of each x-axes line.
653703
rightend_ticks = np.array([len(i)-1 for i in idx]) + np.array(ticks_to_skip)
654-
655-
for ax in fig.axes:
704+
705+
for ax in [rawdata_axes, contrast_axes]:
656706
sns.despine(ax=ax, bottom=True)
657-
707+
658708
ylim = ax.get_ylim()
659709
xlim = ax.get_xlim()
660710
redraw_axes_kwargs['y'] = ylim[0]
661-
711+
662712
for k, start_tick in enumerate(ticks_to_skip):
663713
end_tick = rightend_ticks[k]
664714
ax.hlines(xmin=start_tick, xmax=end_tick,
665715
**redraw_axes_kwargs)
666-
716+
667717
ax.set_ylim(ylim)
668718
del redraw_axes_kwargs['y']
669719

dabest/pytest.ini

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
[pytest]
22
filterwarnings =
33
ignore::UserWarning
4-
ignore::DeprecationWarning
4+
ignore::DeprecationWarning
5+
6+
addopts = --mpl --mpl-baseline-path=dabest/tests/baseline_images
7+
8+
markers =
9+
mpl_image_compare: mark a test as implementing mpl image comparison.

dabest/tests/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# Testing
22

3-
We use [pytest](https://docs.pytest.org/en/latest) to execute the tests. More documentation of the testing paradigm will be added in the near future.
3+
We use [pytest](https://docs.pytest.org/en/latest) to execute the tests. For testing of plot generation, we use the [mpl plugin](https://github.com/matplotlib/pytest-mpl) for pytest. A range of different plots are created, and compared against the baseline images in the `baseline_images` subfolder.
44

55
To run the tests, go to the root of this repo directory and run
6-
76
```shell
87
pytest dabest
98
```
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
38.1 KB
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)