[PYTHON] Statistiques simples qui peuvent être utilisées pour analyser l'effet des mesures sur les sites EC et les codes qui peuvent être utilisés dans le notebook jupyter

introduction

Si vous gérez un site EC général, vous vérifierez ce qui est arrivé à CVR si vous avez pris certaines mesures, et comment les ventes ont changé si vous avez distribué des coupons. Cependant, cela peut se terminer par "CVR a augmenté, mais n'est-ce pas une coïncidence?" Ou "C'est significatif! (D'une manière ou d'une autre)". Dans cet article, je vais vous expliquer comment vérifier la signification à l'aide de statistiques et mettre un outil simple que vous pouvez copier et coller dans le notebook jupyter. L'explication n'est pas très rigoureuse.

Personne cible

--Je ne comprends pas les statistiques

Flou CVR

L'entreprise de M. A vend des pommes sur son site Web. M. A a été informé par son patron de calculer le CVR. CVR est le nombre vendu divisé par PV. Ce jour-là, 30 pièces ont été vendues pour 1000 PV, donc le CVR était de 3,0%. Le lendemain, 28 pièces ont été vendues pour 1000 PV, donc le CVR était de 2,8%, et mon patron s'est mis en colère quand j'ai menti. CVR est une chose floue, vous devez donc le dire dans la mesure où il correspond approximativement. Pour le moment, cela peut être de 2,5% à 3,5%, mais j'aimerais réfléchir de manière plus statistique.

Ici, il existe un phénomène appelé distribution de Bernoulli ou distribution binomiale qui exprime le phénomène selon lequel les personnes qui visitent le site ont deux choix, «acheter» ou «ne pas acheter». Dans le graphique ci-dessous, l'axe horizontal montre le nombre d'articles vendus et l'axe vertical montre la probabilité à ce moment-là. Il y a une forte probabilité que 30 se vendent le plus, et il semble peu probable que ce soit 20 ou moins.

import numpy as np
from scipy.stats import binom, norm
import pandas as pd
p = 0.03
N = 1000
k = np.arange(100)
pd.Series(binom.pmf(k, N, p), name='binom').plot(figsize=(12,6), legend=True)

image.png

Avec cela, il semble que vous puissiez choisir l'endroit avec une grande valeur et proposer une plage qui correspond à peu près. En passant, cette plage est appelée ** intervalle de confiance **, et la probabilité d'entrer dans l'intervalle de confiance est appelée ** coefficient de confiance **. Utilisez les outils ci-dessous pour avoir 95% de chances d'obtenir la plage correcte. (Le nombre d'échantillons est le nombre de PV.)

#Intervalle de confiance du ratio de population
import ipywidgets as widgets
import math
from scipy.stats import binom, norm


def calc(v):
    input_N = w.value
    input_cvr = w2.value
    input_conf = w3.value
    p = input_cvr / 100
    
    #Dans le cas de la distribution binomiale
    max_index = binom.isf((100 - input_conf) / 100, input_N, input_cvr / 100) 
    min_index = binom.isf(input_conf / 100, input_N, input_cvr / 100)
    #Lors de l'approximation d'une distribution normale
    #max_index = norm.isf((100 - input_conf)/100, loc=input_N*p, scale=np.sqrt(input_N*p*(1-p)))
    #min_index = norm.isf(input_conf/100, loc=input_N*p, scale=np.sqrt(input_N*p*(1-p)))
    print(f'{math.floor(min_index / input_N * 10000) / 100}(%) <= CVR <= {math.ceil(max_index / input_N * 10000) / 100}(%)')


button = widgets.Button(description="Calcul")
button.on_click(calc)

w = widgets.IntText(
    value=1000,
    description='Le nombre d'échantillons:',
    disabled=False
)
w2 = widgets.BoundedFloatText(
    value=1,
    min=0,
    max=100.0,
    description='CVR(%):',
    disabled=False
)
w3 = widgets.BoundedIntText(
    value=95,
    min=0,
    description='Coefficient de confiance (%):',
    disabled=False
)

output = widgets.Output()
display(w, output)
display(w2, output)
display(w3, output)
display(button, output)

Une fois exécuté, il ressemblera à l'image ci-dessous. Vous pouvez le saisir librement.

image.png

2,11 (%) <= CVR <= 3,89 (%)! C'est assez large. Un facteur de confiance de 95 à> 90% réduit la plage. Bien qu'il y ait une forte probabilité que vous soyez une «personne entre l'adolescence et la vingtaine, ou une personne entre 30 et 40 ans, ou une personne dans la cinquantaine ou plus», il semble que les «adolescents jusqu'à 20 ans» soient plus susceptibles de s'en sortir. Si vous augmentez le nombre d'échantillons, la plage peut devenir plus petite.

La partie commentée de la source est la distribution Bernoulli [distribution normale](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E5%88%86%E5 Lorsqu'il est proche de% B8% 83). Si le nombre d'échantillons est suffisamment grand, presque le même résultat sera obtenu. Quand je l'exécute, il semble que même si le nombre d'échantillons est de 1 000, il est suffisamment grand. Il ne semble pas nécessaire de faire une approximation, mais supposer une distribution normale est souvent statistiquement facile à faire.

Test de la différence CVR

Comme le produit ne se vendait pas bien, j'ai essayé d'imprimer des images hypnotiques de manière modale et le CVR est passé de 2,0% à 3,0%. Les deux sont de 1000 PV. Cela semble significatif car il a augmenté de 1,5 fois, mais je vais essayer de vérifier (= tester) statistiquement.

Le test le décrit comme "significatif" quand cela se produit, même si on pensait que cela se produirait rarement. La probabilité s'appelle le niveau de signification, et cela est décidé en premier. Dans ce cas, si le niveau de signification est fixé à 5%, on peut dire qu'il est «significatif» si la probabilité qu'il y ait une différence est de 5% ou moins parce qu'elle est passée à 2,0 -> 3,0%.

Je vais omettre les détails, mais comme la distribution de Bernoulli peut être approchée de la distribution normale lorsque le nombre d'échantillons est suffisamment grand, et la différence entre les distributions normales est également la distribution normale (Référence: [Reproductibility of normal distribution](Référence: https://bellcurve.jp/statistics/course/7799.html)) Des résultats précis seront obtenus si le nombre d'échantillons est d'environ 1000.

#Test de différence de ratio de population
import matplotlib.pyplot as plt
import ipywidgets as widgets
import math
from scipy.stats import norm

def calc_plot(v):  
    input_N1 = w_n1.value
    input_cvr1 = w_cvr1.value
    input_N2 = w_n2.value
    input_cvr2 = w_cvr2.value
    input_conf = w3.value

    p1 = input_cvr1 / 100
    p2 = input_cvr2 / 100
    N1 = input_N1
    N2 = input_N2

    p = (N1 * p1 + N2 * p2) / (N1 + N2)
    z = (p2 - p1) / math.sqrt(p * (1 - p) * (1 / N1 + 1 / N2))
    
    min_index = norm.isf(1 - (100 - input_conf)/(2*100), loc=0, scale=1)
    max_index = norm.isf((100 - input_conf)/(2*100), loc=0, scale=1)

    if min_index <= z and z <= max_index:
        print('Insignifiant')
        print(f'|{z}| <= {max_index}')
    else:
        print('Il y a une différence significative!')
        print(f'{max_index} <= |{z}|')


    xlimit = np.array([math.ceil(abs(z)), 5]).max()

    x = np.arange(- xlimit * 100, xlimit * 100)/100
    y = norm.pdf(x)
    plt.figure(figsize=(15, 7)) 
    plt.vlines([min_index, max_index], y.min(), y.max(), "red", linestyles='dashed', label='rejection')
    plt.legend()
    plt.vlines([z], y.min(), y.max(), "black", linestyles='dashed', label='statistics')
    plt.legend()
    plt.plot(x, y,'b-', lw=1, label='norm pdf')
    

button = widgets.Button(description="Calcul")
button.on_click(calc_plot)
w_n1 = widgets.IntText(
    value=10000,
    description='Le nombre d'échantillons:',
    disabled=False
)
w_n2 = widgets.IntText(
    value=12000,
    description='Le nombre d'échantillons:',
    disabled=False
)
w_cvr1 = widgets.BoundedFloatText(
    value=2,
    min=0,
    max=100.0,
    description='CVR(%):',
    disabled=False
)
w_cvr2 = widgets.BoundedFloatText(
    value=3,
    min=0,
    max=100.0,
    description='CVR(%):',
    disabled=False
)
w3 = widgets.BoundedIntText(
    value=95,
    min=0,
    description='Coefficient de confiance (%):',# 100 -Niveau de signification
    disabled=False
)

w_a = widgets.VBox([widgets.Label('A'), w_n1, w_cvr1])
w_b = widgets.VBox([widgets.Label('B'), w_n2, w_cvr2])
whbox = widgets.HBox([w_a, widgets.Label('  '), w_b])

output = widgets.Output()
display(whbox, output)
display(w3, output)
display(button, output)

Lorsque j'ai exécuté le code ci-dessus, j'ai obtenu le résultat affiché dans l'image ci-dessous.

image.png

S'il y a une ligne noire à l'intérieur des deux lignes rouges, cela sera considéré comme "ce genre de flou est possible" et le résultat ne sera pas significatif. Au contraire, si c'est à l'extérieur, c'est significatif. Après avoir regardé le test A / B pendant un moment, si les deux augmentent de 2,0% à> 3,0% à 10000 PV, on peut dire qu'il y a une différence significative. Ce n'est pas un hasard si le flou est réduit en augmentant le nombre d'échantillons, mais la différence est de 1,0%. Le point à noter ici est que «non significatif» ne peut être considéré comme insignifiant. De plus, le fait que les lignes noires et rouges soient si éloignées n'est pas très significatif ou très significatif. Il y a deux choix, qu'il soit significatif ou non. Dans cet esprit, je pense que l'intervalle de confiance contient plus d'informations que les résultats des tests.

Si B est plus grand que A

Dans le cas ci-dessus, nous avons testé s'il y avait une différence, mais en réalité, nous voulons généralement seulement savoir si le CVR a augmenté en prenant des mesures. Dans ce cas, utilisez le ** test unilatéral **. (Dans le cas ci-dessus, cela s'appelle un test bilatéral.) Lors d'un test unilatéral, remplacez calc_plot dans le code python ci-dessus par le code ci-dessous.

    input_N1 = w_n1.value
    input_cvr1 = w_cvr1.value
    input_N2 = w_n2.value
    input_cvr2 = w_cvr2.value
    input_conf = w3.value

    p1 = input_cvr1 / 100
    p2 = input_cvr2 / 100
    N1 = input_N1
    N2 = input_N2

    p = (N1 * p1 + N2 * p2) / (N1 + N2)
    z = (p2 - p1) / math.sqrt(p * (1 - p) * (1 / N1 + 1 / N2))
    max_index = norm.isf((100 - input_conf)/100, loc=0, scale=1)

    if z <= max_index:
        print('Insignifiant')
        print(f'|{z}| <= {max_index}')
    else:
        print('Il y a une différence significative!')
        print(f'{max_index} <= |{z}|')


    xlimit = np.array([math.ceil(abs(z)), 5]).max()

    x = np.arange(- xlimit * 100, xlimit * 100)/100
    y = norm.pdf(x)
    plt.figure(figsize=(15, 7)) 
    plt.vlines([max_index], y.min(), y.max(), "red", linestyles='dashed', label='rejection')
    plt.legend()
    plt.vlines([z], y.min(), y.max(), "black", linestyles='dashed', label='statistics')
    plt.legend()
    plt.plot(x, y,'b-', lw=1, label='norm pdf')

image.png

Dans ce cas, il est significatif qu'il y ait une ligne noire à droite de la ligne rouge. Il est plus facile de faire un jugement significatif avec un petit nombre d'échantillons car vous ne pouvez voir que si ce dernier est plus élevé.

Intervalle de confiance du prix d'achat moyen

Même si le CVR est en hausse, il est possible que le prix d'achat moyen soit en baisse. (Lorsque le montant d'utilisation minimum est augmenté lors de la distribution du coupon, etc.) Calculons combien le prix d'achat moyen va fluctuer. En général, le revenu annuel a une distribution normale logarithmique et il existe une corrélation positive entre le revenu annuel et le prix d'achat, de sorte que l'on pense que le prix d'achat a également une distribution similaire sur les sites communautaires qui traitent divers produits. (Référence: Exemple de distribution normale logarithmique et moyenne, variance)

#Enregistrer la distribution normale
from scipy.stats import lognorm
x = np.arange(1, 100000) / 1
A = 1.5000e+04 #Prix d'achat moyen
B = 1.5000e+04 ** 2 #Distribué
s = np.sqrt(np.log(B/(A ** 2) + 1))
mu = np.log(A) - (s ** 2 / 2)
y = pd.Series(lognorm.pdf(x, s, 0, np.exp(mu)))
y.index = x
y.plot(figsize=(12, 6))

image.png

Si vous les combinez et prenez la moyenne, vous obtiendrez à nouveau une distribution normale. Le code ci-dessous est un histogramme du résultat de la répétition de l'opération de calcul de la moyenne de 500 pièces 100 000 fois. Selon Center Pole Limitation, l'histogramme moyen sera similaire s'il y a suffisamment d'échantillons même s'il ne s'agit pas d'une distribution normale logarithmique. .. Le nombre d'échantillons suffisant dépend de la distribution, mais environ 1 000 semblent être bons.

means = []
n = 500
for i in range(0, 100000):
    means.append(np.array(lognorm.rvs(s, 0, np.exp(mu), size=n)).mean())
    
pd.Series(means).hist(bins=100)

image.png

L'image ci-dessous s'appelle un graphique QQ, et si le bleu est proche de la ligne droite rouge, il est proche de la distribution normale.

import pylab
stats.probplot(means, dist="norm", plot=pylab)

image.png

Le code ci-dessous calcule l'intervalle de confiance pour une distribution normale. Le "nombre d'échantillons" cette fois n'est pas le PV, mais le nombre d'achats.

#Intervalle de confiance du prix d'achat moyen
import ipywidgets as widgets
import math
import numpy as np
import pandas as pd
from scipy.stats import binom, norm

def calc(v):
    n = w.value
    mu = w2.value
    sigma = w3.value
    input_conf = w4.value
    
    max_index = norm.isf((100 - input_conf)/100, loc=mu, scale=sigma / np.sqrt(n))
    min_index = norm.isf(input_conf/100, loc=mu, scale=sigma / np.sqrt(n))
    print(f'{min_index} <=Prix d'achat moyen<= {max_index}')


button = widgets.Button(description="Calcul")
button.on_click(calc)

w = widgets.IntText(
    value=1000,
    description='Le nombre d'échantillons:',
    disabled=False
)
w2 = widgets.FloatText(
    value=15000,
    description='Prix d'achat moyen:',
    disabled=False
)
w3 = widgets.FloatText(
    value=15000,
    description='écart-type:',
    disabled=False
)
w4 = widgets.BoundedIntText(
    value=95,
    min=0,
    description='Coefficient de confiance (%):',
    disabled=False
)

output = widgets.Output()
display(w, output)
display(w2, output)
display(w3, output)
display(w4, output)
display(button, output)

image.png

Section de vente fiable

Ventes = nombre de PV x CVR x prix d'achat moyen Par conséquent, il est calculé en multipliant la section de confiance du CVR x le prix d'achat moyen.

#Section de vente fiable
import ipywidgets as widgets
import math
from scipy.stats import binom, norm


def calc(v):
    n = w_n.value
    cvr = w_cvr.value
    mu = w_mu.value
    sigma = w_s.value
    input_conf = w_conf.value
    p = cvr / 100
    
    min_index_sales = norm.isf(1 - (100 - input_conf)/(2*100), loc=mu, scale=sigma / np.sqrt(n*p))
    max_index_sales = norm.isf((100 - input_conf)/(2*100), loc=mu, scale=sigma / np.sqrt(n*p))
    max_index = norm.isf((100 - input_conf)/100, loc=n*p, scale=np.sqrt(n*p*(1-p))) / n
    min_index = norm.isf(input_conf/100, loc=n*p, scale=np.sqrt(n*p*(1-p))) / n
    print(f'{n * min_index * min_index_sales} <=Gains<= {n * max_index * max_index_sales}')


button = widgets.Button(description="Calcul")
button.on_click(calc)

w_n = widgets.IntText(
    value=10000,
    description='Nombre d'échantillons (PV):',
    disabled=False
)
w_cvr = widgets.BoundedFloatText(
    value=12.4,
    min=0,
    max=100.0,
    description='CVR(%):',
    disabled=False
)
w_mu = widgets.FloatText(
    value=18303,
    description='moyenne:',
    disabled=False
)
w_s = widgets.FloatText(
    value=15217,
    description='écart-type:',
    disabled=False
)
w_conf = widgets.BoundedIntText(
    value=90,
    min=0,
    description='Coefficient de confiance (%):',
    disabled=False
)

output = widgets.Output()
display(w_n, output)
display(w_cvr, output)
display(w_mu, output)
display(w_s, output)
display(w_conf, output)
display(button, output)

image.png

En réalité, le CVR et le prix d'achat moyen ne sont pas toujours indépendants. Si le prix d'achat moyen augmente et le CVR diminue, je pense que l'intervalle de confiance est susceptible de se rétrécir. J'étais épuisé avant de l'examiner en détail ...

Recommended Posts

Statistiques simples qui peuvent être utilisées pour analyser l'effet des mesures sur les sites EC et les codes qui peuvent être utilisés dans le notebook jupyter
Goroutine (contrôle parallèle) utilisable sur le terrain
[Django] Noms de champs pouvant être utilisés pour le modèle utilisateur, l'enregistrement des utilisateurs et les méthodes de connexion
Goroutine utilisable sur le terrain (édition errgroup.Group)
[Python] Un programme pour trouver le nombre de pommes et d'oranges qui peuvent être récoltées
Comprendre les probabilités et les statistiques qui peuvent être utilisées pour la gestion des progrès avec un programme python
Un minuteur (ticker) qui peut être utilisé sur le terrain (peut être utilisé n'importe où)
Module standard Python utilisable en ligne de commande
Remplissage facile des données pouvant être utilisées dans le traitement du langage naturel
Masquer l'avertissement selon lequel zsh peut être utilisé par défaut sur Mac
Je voulais créer rapidement un serveur de messagerie utilisable librement avec postfix + dovecot sur EC2
Contrôle QPS utilisable sur le terrain (Rate Limit) Limite l'exécution à n fois par seconde
[Python3] Code qui peut être utilisé lorsque vous souhaitez modifier l'extension d'une image à la fois
Notes personnelles des opérations liées aux pandas qui peuvent être utilisées dans la pratique
Pour générer une valeur au milieu d'une cellule avec Jupyter Notebook
Programme d'installation facile et programme de mise à jour automatique pouvant être utilisé dans n'importe quelle langue
Un mémorandum expliquant comment exécuter la commande magique! Sudo dans Jupyter Notebook
Créez un environnement PYNQ sur Ultra96 V2 et connectez-vous à Jupyter Notebook
Une bibliothèque pour la science des données "Je veux faire ça" sur le bloc-notes Jupyter
Comment rendre la largeur de police du notebook jupyter mis dans pyenv égale
Liste des outils qui peuvent être utilisés pour essayer facilement l'analyse des émotions des phrases japonaises avec Python (essayez avec google colab)
Fonctions pouvant être utilisées dans l'instruction for
L'histoire du démarrage du notebook Jupyter de python2.x à l'aide de docker (écrasé samedi et dimanche)
Résumé des méthodes d'analyse de données statistiques utilisant Python qui peuvent être utilisées en entreprise
des modèles de tracé et de mise en page qui sont susceptibles d'être utilisés dans les diagrammes de dispersion
Comment filtrer les clés externes qui peuvent être sélectionnées sur l'écran d'administration de Django
Visualisation des informations géographiques de R et Python qui peuvent être exprimées par Power BI
[Python] Introduction au scraping WEB | Résumé des méthodes pouvant être utilisées avec webdriver
Remarques sur l'utilisation de StatsModels qui peuvent utiliser la régression linéaire et GLM en python
Lisez le fichier csv avec le notebook jupyter et écrivez le graphique l'un sur l'autre
Algorithmes de base utilisables par les pros de la compétition
le noyau du notebook jupyter ne peut plus se connecter
Enregistrement d'image ANT qui peut être utilisé en 5 minutes
[Django] À propos des utilisateurs pouvant être utilisés sur un modèle
Comment gérer le phénomène que Python (notebook Jupyter) exécuté sur WSL devient abandonné
Comment démarrer un serveur WEB simple qui peut exécuter des cgi de php et python
Installez Mecab et CaboCha sur ubuntu16.04LTS afin qu'il puisse être utilisé à partir de la série python3
Comment configurer un serveur SMTP simple qui peut être testé localement en Python
[Python3] Code qui peut être utilisé lorsque vous souhaitez redimensionner des images dossier par dossier
Une version simple des statistiques gouvernementales (contrôle de l'immigration) facile à gérer avec jupyter
Comment définir des variables pouvant être utilisées dans toute l'application Django ~ Utile pour les modèles, etc. ~
En arrangeant la différence entre «statistiques» et «apprentissage automatique», je peux voir la raison pour laquelle «l'apprentissage automatique» ne peut pas être utilisé dans de nombreuses entreprises!