Introduction à la vérification de l'efficacité Chapitre 3 écrit en Python

introduction

Introduction à la vérification de l'efficacité - Raisonnement causal pour une comparaison correcte / Bases de l'économie quantitative Reproduire le code source en Python Faire.

J'ai déjà un Exemple d'implémentation de grand ancêtre, mais je le laisserai comme mémo pour mon étude.

Cet article couvre le chapitre 3. Le code est également posté sur github. De plus, les noms de variables et le contenu du traitement sont essentiellement implémentés dans le livre.

Retour logistique

Vous pouvez implémenter une régression logistique avec des modèles scicit-learn ou stats.

scikit-learn

scikit-learn doit être factice.

Retour logistique de sklearn


from sklearn.linear_model import LogisticRegression

X = pd.get_dummies(biased_data[['recency', 'history', 'channel']])  #Faire du canal une variable factice
treatment = biased_data['treatment'] 

model = LogisticRegression().fit(X, treatment)

statsmodels

Utilisez les classes glm ou logit.

statsmodels n'a pas besoin d'être factice, mais il semble que certaines valeurs de catégorie ne soient pas utilisées dans le modèle. (Channel = Multichannel n'est pas utilisé dans l'exemple ci-dessous. La cause n'est pas claire.) Si vous êtes inquiet, vous pouvez en faire un mannequin et il correspondra au résultat de sklearn.

Analyse de régression des modèles de statistiques


from statsmodels.formula.api import glm, logit

model = glm('treatment ~ history + recency + channel', data=biased_data, family=sm.families.Binomial()).fit()
# model = logit('treatment ~ history + recency + channel', data=biased_data).fit()  #Même résultat que ci-dessus

Estimation à l'aide du score de propension

«L'appariement des scores de propension» et «IPW», qui sont mentionnés comme méthodes d'estimation utilisant des scores de propension, peuvent être mis en œuvre sur DoWhy.

Avant d'effectuer chaque analyse, définissez une instance pour l'inférence causale commune. À ce moment-là, notez que la variable d'intervention doit être booléenne.

Préparation


from dowhy import CausalModel

biased_data = biased_data.astype({"treatment":'bool'}, copy=False)  #traitement booléen

model=CausalModel(
    data = biased_data,
    treatment='treatment',
    outcome='spend',
    common_causes=['recency', 'history', 'channel']
)

Appariement du score de propension

Correspondance la plus proche

L'appariement le plus proche est une méthode d'appariement 1: 1 entre les échantillons avec intervention et les échantillons sans intervention, qui sont expliqués dans le livre, avec des scores de propension similaires. Dans ce livre, seul ATT est estimé, mais DoWhy peut également estimer ATE. Notez que la valeur par défaut est ATE. Puisque les deux sont calculés par l'implémentation interne, ce serait bien si vous pouviez obtenir les deux.

De plus, cet ATE semble être calculé à partir de l'ATT et de l'ATC (valeur attendue de l'effet d'intervention sur l'échantillon de non-intervention).

Correspondance la plus proche


nearest_ate = model.estimate_effect(
    identified_estimand, 
    method_name="backdoor.propensity_score_matching",
    target_units='ate',
)
nearest_att = model.estimate_effect(
    identified_estimand, 
    method_name="backdoor.propensity_score_matching",
    target_units='att', 
)

print(f'ATE: {nearest_ate.value}')
print(f'ATT: {nearest_att.value}')

Appariement stratifié

Bien que cela ne soit pas mentionné dans ce livre, l'appariement stratifié est également mis en œuvre.

En regardant le code source, il semble que les données soient divisées dans la couche num_strata par le même nombre et estimées. clipping_threshold compare le nombre de cas avec et sans intervention dans chaque couche, et exclut les couches inférieures à cette valeur. (Probablement pour exclure la couche avec extrêmement peu de données d'intervention)

Appariement stratifié


stratification_ate = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_stratification",
    target_units='ate',
    method_params={'num_strata':50, 'clipping_threshold':5},
)
stratification_att = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_stratification",
    target_units='att',
    method_params={'num_strata':50, 'clipping_threshold':5},
)

print(f'ATE: {stratification_ate.value}')
print(f'ATT: {stratification_att.value}')

IPW

IPW


ipw_estimate = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_weighting",
    target_units='ate',
)

print(f'ATE: {ipw_estimate.value}')

(Bonus) Analyse de régression

L'analyse de régression effectuée au chapitre 2 est également possible.

python


estimate = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.linear_regression", #test_significance=True
)

print('Causal Estimate is ' + str(estimate.value))

Visualisation de la différence moyenne standardisée

La visualisation des différences moyennes standardisées pour confirmer l'équilibre des covariables n'est pas prise en charge. Par conséquent, c'est gênant, mais il est calculé par vous-même.

Appariement du score de propension

Il semble que vous ne puissiez pas vous référer à la correspondance de l'appariement par score de propension, alors mettez en œuvre l'appariement vous-même. L'implémentation est basée sur Around here.

Je ne sais pas comment trouver la «distance» et la calculer correctement, alors ne l'utilisez pas comme référence.

Différence moyenne standard d'appariement du score de propension


from sklearn.neighbors import NearestNeighbors

#Calcul de l'ASAM
def calculate_asam(data, treatment_column, columns):
  treated_index = data[treatment_column]
  data = pd.get_dummies(data[columns])

  asam = data.apply(lambda c: abs(c[treated_index].mean() - c[~treated_index].mean()) / c.std())
  asam['distance'] = np.sqrt(np.sum(asam**2))  #Je ne comprends pas la définition
  return asam

#Appariement du score de propension
def get_matching_data(data, treatment_column, propensity_score):
  data['ps'] = propensity_score
  treated = data[data[treatment_column] == 1]
  control = data[data[treatment_column] == 0]

  #Faire correspondre les échantillons avec l'intervention
  control_neighbors = NearestNeighbors(n_neighbors=1, algorithm='ball_tree').fit(control['ps'].values.reshape(-1, 1))
  distances, indices = control_neighbors.kneighbors(treated['ps'].values.reshape(-1, 1))
  #Vous pouvez définir le seuil de correspondance avec les distances(Pas compatible)

  matching_data = pd.concat([treated, control.iloc[indices.flatten()]])
  return matching_data

matching_data = get_matching_data(biased_data[['treatment', 'recency', 'channel', 'history']], 'treatment', nearest_ate.propensity_scores)
unadjusted_asam = calculate_asam(biased_data, 'treatment', ['recency', 'history', 'channel'])
matching_adjusted_asam = calculate_asam(matching_data, 'treatment', ['recency', 'history', 'channel'])

IPW

Avec DescrStatsW, vous pouvez facilement calculer des statistiques pondérées. Cependant, la valeur non pondérée est-elle légèrement différente du résultat de numpy? Soyez donc prudent lorsque vous le manipulez.

Différence moyenne normalisée IPW


from statsmodels.stats.weightstats import DescrStatsW

def calculate_asam_weighted(data, treatment_column, columns, propensity_score):
  data = pd.get_dummies(data[[treatment_column] + columns])
  data['ps'] = propensity_score
  data['weight'] = data[[treatment_column, 'ps']].apply(lambda x: 1 / x['ps'] if x[treatment_column] else 1 / (1 - x['ps']), axis=1)

  asam_dict = dict()
  for column_name, column_value in data.drop([treatment_column, 'ps', 'weight'], axis=1).iteritems():
    treated_stats = DescrStatsW(column_value[data[treatment_column]], weights=data[data[treatment_column]]['weight'])
    control_stats = DescrStatsW(column_value[~data[treatment_column]], weights=data[~data[treatment_column]]['weight'])
    asam_dict[column_name] = abs(treated_stats.mean - control_stats.mean) / treated_stats.std

  asam = pd.Series(asam_dict)
  asam['distance'] = np.sqrt(np.sum(asam**2))  #Je ne comprends pas la définition
  return asam

ipw_adjusted_asam = calculate_asam_weighted(biased_data, 'treatment', ['recency', 'history', 'channel'], ipw_estimate.propensity_scores)

Visualisation

Visualisation


%matplotlib inline
import matplotlib.pyplot as plt

plt.scatter(unadjusted_asam, unadjusted_asam.index, label='unadjusted')
plt.scatter(matching_adjusted_asam, matching_adjusted_asam.index, label='adjusted(matching)')
plt.scatter(ipw_adjusted_asam, ipw_adjusted_asam.index, label='adjusted(ipw)')
plt.vlines(0.1, 0, len(unadjusted_asam)-1, linestyles='dashed')
plt.legend()
plt.show()

image.png

En quelque sorte, l'IPW est meilleur, mais probablement parce que l'appariement par score de propension permet la duplication des échantillons correspondants dans l'appariement du plus proche voisin et est biaisé en faveur de certains échantillons.

Relation

Recommended Posts

Introduction à la vérification de l'efficacité Chapitre 3 écrit en Python
Introduction à la vérification de l'efficacité Chapitre 2 écrit en Python
Introduction à la vérification de l'efficacité Chapitre 1 écrit en Python
Introduction à la vérification des effets Rédaction des chapitres 4 et 5 en Python
J'ai écrit "Introduction à la vérification des effets" en Python
[Introduction à Python3 Jour 13] Chapitre 7 Chaînes de caractères (7.1-7.1.1.1)
[Introduction à Python3 Jour 14] Chapitre 7 Chaînes de caractères (7.1.1.1 à 7.1.1.4)
[Introduction à Python3 Jour 15] Chapitre 7 Chaînes de caractères (7.1.2-7.1.2.2)
[Introduction à Python3 Day 21] Chapitre 10 Système (10.1 à 10.5)
"Introduction à la vérification des effets Chapitre 3 Analyse utilisant le score de propension" + α est essayé en Python
Code de vérification de la série Fourier écrit en Python
[Introduction à Python] Comment utiliser la classe en Python?
[Introduction à Python3, jour 17] Chapitre 8 Destinations de données (8.1-8.2.5)
[Introduction à Python3, jour 17] Chapitre 8 Destinations de données (8.3-8.3.6.1)
[Introduction à Python3 Jour 19] Chapitre 8 Destinations de données (8.4-8.5)
[Introduction à Python3 Day 18] Chapitre 8 Destinations de données (8.3.6.2 à 8.3.6.3)
Introduction aux vecteurs: Algèbre linéaire en Python <1>
[Introduction à Python3 Jour 12] Chapitre 6 Objets et classes (6.3-6.15)
tse --Introduction à l'éditeur de flux de texte en Python
Introduction au langage Python
[Introduction à Python3, jour 22] Chapitre 11 Traitement parallèle et mise en réseau (11.1 à 11.3)
Introduction à OpenCV (python) - (2)
[Introduction à Python3 Jour 11] Chapitre 6 Objets et classes (6.1-6.2)
[Introduction à Python3, Jour 23] Chapitre 12 Devenir un Paisonista (12.1 à 12.6)
[Introduction à Python3 Jour 20] Chapitre 9 Démêler le Web (9.1-9.4)
[Introduction à Python3 Jour 8] Chapitre 4 Py Skin: Structure du code (4.1-4.13)
Analyser une chaîne JSON écrite dans un fichier en Python
[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 3] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 2] Introduction à Python avec 100 coups de traitement du langage
Introduction à l'algèbre linéaire avec Python: Décomposition A = LU
[Livre technique] Introduction à l'analyse de données avec Python -1 Chapitre Introduction-
[Chapitre 4] Introduction à Python avec 100 coups de traitement du langage
Introduction à Python Django (2) Win
Connectez-vous au site Web en Python
Introduction à la communication série [Python]
Modèles de conception en Python: introduction
Parler avec Python [synthèse vocale]
[Introduction à Python] <liste> [modifier le 22/02/2020]
Introduction à Python (version Python APG4b)
Gacha écrit en Python -BOX Gacha-
Une introduction à la programmation Python
Comment développer en Python
Introduction à Python pour, pendant
Publier sur Slack en Python
[Introduction à l'application Udemy Python3 +] 36. Utilisation de In et Not
[Introduction à Python3 Jour 3] Chapitre 2 Composants Py: valeurs numériques, chaînes de caractères, variables (2.2 à 2.3.6)
[Introduction à Python3 Jour 2] Chapitre 2 Composants Py: valeurs numériques, chaînes de caractères, variables (2.1)
[Introduction à Python3 Jour 4] Chapitre 2 Composants Py: valeurs numériques, chaînes de caractères, variables (2.3.7 à 2.4)
Cool Lisp écrit en Python: Hy
[Présentation de l'application Udemy Python3 +] 58. Lambda
[Présentation de l'application Udemy Python3 +] 31. Commentaire
[Python] Comment faire PCA avec Python
Introduction à la bibliothèque de calcul numérique Python NumPy
Entraine toi! !! Introduction au type Python (conseils de type)
[Introduction à Python3 Jour 1] Programmation et Python
Convertir Markdown en PDF en Python
Comment collecter des images en Python
100 Language Processing Knock Chapitre 1 en Python
[Introduction à Python] <numpy ndarray> [modifier le 22/02/2020]
[Présentation de l'application Udemy Python3 +] 57. Décorateur