Calcul de la taille minimale de l’échantillon pour les tests A/B dans les modèles statistiques : comment et pourquoi
Une méthode d’optimisation numérique populaire et très performante est Méthode Brents. La méthode Brents est un algorithme de recherche de racine qui combine diverses techniques telles que la méthode de bissection, la méthode sécante et l’interpolation quadratique inverse. De plus amples détails sur sa mise en œuvre dans Statsmodels peuvent être trouvés ici.
En Python, l’implémentation ressemble à ceci :
def solve_power(self, effect_size=None, nobs1=None, alpha=None, power=None,
ratio=1., alternative='two-sided'):
print('--- Arguments: ---')
print('effect_size:', effect_size, 'nobs1:', nobs1, 'alpha:', alpha, 'power:', power, 'ratio:', ratio, 'alternative:', alternative, '\n')# Check that only nobs1 is None
kwds = dict(effect_size=effect_size, nobs1=nobs1, alpha=alpha,
power=power, ratio=ratio, alternative=alternative)
key = [k for k,v in kwds.items() if v is None]
assert(key == ['nobs1'])
# Check that the effect_size is not 0
if kwds['effect_size'] == 0:
raise ValueError('Cannot detect an effect-size of 0. Try changing your effect-size.')
# Initialize the counter
self._counter = 0
# Define the function that we want to find the root of
# We want to find nobs1 s.t. current power = target power, i.e. current power - target power = 0
# So func = current power - target power
def func(x):
kwds['nobs1'] = x
target_power = kwds.pop('power') # always the same target power specified in keywords, e.g. 0.8
current_power = self.power(**kwds) # current power given the current nobs1, note that self.power does not have power as an argument
kwds['power'] = target_power # add back power to kwds
fval = current_power - target_power
print(f'Iteration {self._counter}: nobs1 = {x}, current power - target power = {fval}')
self._counter += 1
return fval
# Get the starting values for nobs1, given the brentq_expanding algorithm
# In the original code, this is the self.start_bqexp dictionary set up in the __init__ method
bqexp_fit_kwds = {'low': 2., 'start_upp': 50.}
# Solve for nobs1 using brentq_expanding
print('--- Solving for optimal nobs1: ---')
val, _ = brentq_expanding(func, full_output=True, **bqexp_fit_kwds)
return val
1.2. Écriture d’une version allégée de tt_ind_solve_power qui est une implémentation exacte de la dérivation statistique et produit le même résultat que la fonction d’origine
Le fichier source dans Statsmodels est disponible ici. Bien que la fonction d’origine soit écrite pour être plus puissante, sa généralisabilité rend également plus difficile l’intuition du fonctionnement du code.
J’ai donc parcouru le code source ligne par ligne et l’ai simplifié de 1 600 lignes de code à 160, et de plus de 10 fonctions à seulement 2, tout en m’assurant que l’implémentation reste identique.
Le code simplifié ne contient que deux fonctions sous la classe TTestIndPower, suivant exactement la dérivation statistique expliquée dans la partie 1 :
- pouvoirqui calcule la puissance en fonction d’une taille d’échantillon
- résoudre_powerqui trouve la taille minimale de l’échantillon permettant d’atteindre une puissance cible à l’aide de la méthode Brents
Il s’agit du code complet de la version allégée avec un test pour vérifier qu’elle produit le même résultat que la fonction d’origine :