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.
--Je ne comprends pas les statistiques
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)
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.
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.
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.
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.
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')
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é.
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))
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)
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)
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)
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)
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