|
1 | | -"""example function to analyze reaction times |
2 | | -- given a data frame with RT and accuracy, |
3 | | -compute mean RT for correct trials and mean accuracy |
| 1 | +"""Example class to analyze reaction times. |
| 2 | +
|
| 3 | +Given a data frame with RT and accuracy, compute mean RT for correct trials and |
| 4 | +mean accuracy. |
4 | 5 | """ |
5 | | -# %% |
6 | 6 | import pandas as pd |
7 | 7 |
|
8 | 8 |
|
9 | | -# %% |
10 | 9 | class RTAnalysis: |
11 | | - """[summary]""" |
| 10 | + """Response time (RT) analysis.""" |
12 | 11 |
|
13 | 12 | def __init__(self, outlier_cutoff_sd=None): |
14 | | - """ |
15 | | - RT analysis |
| 13 | + """Initialize a new RTAnalysis instance. |
16 | 14 |
|
17 | | - Parameters: |
18 | | - ----------- |
19 | | - outlier_cutoff_sd: standard deviation cutoff for long RT outliers (default: no cutoff) |
| 15 | + Parameters |
| 16 | + ---------- |
| 17 | + outlier_cutoff_sd : float, optional |
| 18 | + Standard deviation cutoff for long RT outliers, by default None |
20 | 19 | """ |
21 | 20 | self.outlier_cutoff_sd = outlier_cutoff_sd |
22 | | - self.meanrt_ = None |
23 | | - self.meanacc_ = None |
| 21 | + self.mean_rt_ = None |
| 22 | + self.mean_accuracy_ = None |
24 | 23 |
|
25 | 24 | def fit(self, rt, accuracy, verbose=True): |
26 | | - """[summary] |
27 | | -
|
28 | | - Args: |
29 | | - rt (Series of floats): response times for each trial |
30 | | - accuracy (Series of booleans): accuracy for each trial |
| 25 | + """Fit response time to accuracy. |
| 26 | +
|
| 27 | + Parameters |
| 28 | + ---------- |
| 29 | + rt : pd.Series |
| 30 | + Response time per trial |
| 31 | + accuracy : pd.Series |
| 32 | + Accuracy per trial |
| 33 | + verbose : bool, optional |
| 34 | + Whether to print verbose output or not, by default True |
| 35 | +
|
| 36 | + Raises |
| 37 | + ------ |
| 38 | + ValueError |
| 39 | + RT/accuracy length mismatch |
| 40 | + ValueError |
| 41 | + Accuracy is 0 |
31 | 42 | """ |
32 | | - |
33 | 43 | rt = self._ensure_series_type(rt) |
34 | 44 | accuracy = self._ensure_series_type(accuracy) |
35 | 45 |
|
36 | | - try: |
37 | | - assert rt.shape[0] == accuracy.shape[0] |
38 | | - except AssertionError as e: |
39 | | - raise ValueError("rt and accuracy must be the same length!") from e |
| 46 | + self._validate_length(rt, accuracy) |
40 | 47 |
|
41 | | - # ensure that accuracy values are boolean |
42 | | - assert not set(accuracy.unique()).difference([True, False]) |
| 48 | + # Ensure that accuracy values are boolean. |
| 49 | + assert accuracy.dtype == bool |
43 | 50 |
|
44 | | - if self.outlier_cutoff_sd is not None: |
45 | | - cutoff = rt.std() * self.outlier_cutoff_sd |
46 | | - if verbose: |
47 | | - print(f"outlier rejection excluded {(rt > cutoff).sum()} trials") |
48 | | - rt = rt.mask(rt > cutoff) |
| 51 | + rt = self.reject_outlier_rt(rt, verbose=verbose) |
49 | 52 |
|
50 | | - self.meanacc_ = accuracy.mean() |
| 53 | + self.mean_accuracy_ = accuracy.mean() |
51 | 54 | try: |
52 | | - assert self.meanacc_ > 0 |
| 55 | + assert self.mean_accuracy_ > 0 |
53 | 56 | except AssertionError as e: |
54 | | - raise ValueError("accuracy is zero") from e |
| 57 | + raise ValueError("Accuracy is zero!") from e |
55 | 58 |
|
56 | 59 | rt = rt.mask(~accuracy) |
57 | | - self.meanrt_ = rt.mean() |
| 60 | + self.mean_rt_ = rt.mean() |
58 | 61 |
|
59 | 62 | try: |
60 | 63 | assert rt.min() > 0 |
61 | 64 | except: |
62 | 65 | raise ValueError( "negative response times found") |
63 | 66 | if verbose: |
64 | | - print(f"mean RT: {self.meanrt_}") |
65 | | - print(f"mean accuracy: {self.meanacc_}") |
| 67 | + print(f"mean RT: {self.mean_rt_}") |
| 68 | + print(f"mean accuracy: {self.mean_accuracy_}") |
| 69 | + |
| 70 | + @staticmethod |
| 71 | + def _validate_length(rt, accuracy): |
| 72 | + """Validate response time and accuracy series lengths. |
| 73 | +
|
| 74 | + Parameters |
| 75 | + ---------- |
| 76 | + rt : pd.Series |
| 77 | + Response time values |
| 78 | + accuracy : _type_ |
| 79 | + Accuracy values |
| 80 | +
|
| 81 | + Raises |
| 82 | + ------ |
| 83 | + ValueError |
| 84 | + Length mismatch |
| 85 | + """ |
| 86 | + same_length = rt.shape[0] == accuracy.shape[0] |
| 87 | + try: |
| 88 | + assert same_length |
| 89 | + except AssertionError as e: |
| 90 | + raise ValueError("RT and accuracy must be the same length!") from e |
| 91 | + |
66 | 92 |
|
67 | 93 | @staticmethod |
68 | 94 | def _ensure_series_type(var): |
69 | | - """return variable as a pandas Series or raise exception if |
70 | | - not possible |
| 95 | + """Return variable as a pandas Series. |
71 | 96 |
|
72 | | - Args: |
73 | | - var (array-like): variable to convert |
| 97 | + Parameters |
| 98 | + ---------- |
| 99 | + var : Iterable |
| 100 | + Variable to be converted |
74 | 101 |
|
75 | | - Returns: |
76 | | - series (pandas Series): converted variable |
| 102 | + Returns |
| 103 | + ------- |
| 104 | + pd.Series |
| 105 | + Variable values as a pandas Series |
77 | 106 | """ |
78 | | - |
79 | | - if type(var) is not pd.core.series.Series: |
| 107 | + if not isinstance(var, pd.Series): |
80 | 108 | var = pd.Series(var) |
81 | 109 | return var |
82 | 110 |
|
83 | | -# %% |
| 111 | + def reject_outlier_rt(self, rt, verbose=True): |
| 112 | + if self.outlier_cutoff_sd is None: |
| 113 | + return rt |
| 114 | + cutoff = rt.std() * self.outlier_cutoff_sd |
| 115 | + if verbose: |
| 116 | + n_excluded = (rt > cutoff).sum() |
| 117 | + print(f"Outlier rejection excluded {n_excluded} trials.") |
| 118 | + return rt.mask(rt > cutoff) |
0 commit comments