<jupyter_start><jupyter_text>Natality Experiment on the DAME-FLAME Python PackageI use the DAME-FLAME Python package, which is publicly available for download [here](https://pypi.org/project/dame-flame/), on a real dataset, aiming to examine the effect of "extreme smoking" during pregancy on a newborn's birthweight. Overall, the results of the DAME-FLAME package lead us to believe that smoking during pregnancy causes significantly lower birthweight of infants. It also leads us to believe that there is little to no heterogeneity in *conditional treatment effects* of a mother's smoking on birthweight. In other words, when we examine groupings of characteristics of mothers and infants (including race, gender of infant, etc), then we don't see evidence that there exists some mothers and infants for whom the impact of maternal smoking on infant birthweight is different than the outcomes for other types of mothers and infants. I use the 2010 US Natality dataset from the National Vital Statistics System of the National Center for Health Statistics (2010), available here: http://www2.nber.org/data/vital-statistics-natality-data.html.The accompanying user guide to the dataset is also linked on this URL, as "documentation". This code and analysis was written by <NAME>, following similar work done by <NAME>, in the paper [FLAME: A Fast Large-scale Almost Matching Exactly Approach to Causal Inference](https://arxiv.org/abs/1707.06315). Notable differences between the two are that the latter uses a slightly modified algorithm (Wang uses FlameDB, I use FlameBit), and this experiment drops all units that have missing data rather than matching on units with missing data while skipping matching on missing covariates (Both options are available in the `dame-flame` package! Use the `missing_data_replace` parameter). I also cut back on the dataset size for processing time. This experiment aims to build upon the analysis on maternal smoking and infant birthweight shown in [Kondracki (2020)](https://link.springer.com/content/pdf/10.1186/s12884-020-02981-1.pdf). For questions on this code, or on the DAME-FLAME Python Package, please contact Neha at neha.r.gupta "at" duke "dot" edu.<jupyter_code>import pandas as pd
import numpy as np
import dame_flame
import random
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>Data Cleaning We begin by reading in the data file from the URL and cleaning it. We take a randomly chosen small sample, the variable `p`, for this experiment.<jupyter_code>p = 0.1

df = pd.read_csv('http://data.nber.org/natality/2010/natl2010.csv.zip',
         header=0, 
         skiprows=lambda i: i>0 and random.random() > p)
# Restrict to singular births only. This is the DPLURAL variable
df = df[df['dplural'] == 1]

# need to also restrict to standard term births. This is 37 weeks+, as defined by Kondracki (2020)
df = df[df['gestrec3'] == 2]

# These are missing from the weight, I presume from the User Guide. 
df = df[df['dbwt'] != 9999]<jupyter_output><empty_output><jupyter_text>Below, I recode cigarette usage. I follow [Kondracki (2020)](https://link.springer.com/content/pdf/10.1186/s12884-020-02981-1.pdf) and define "extreme smoking" as 10+ cigarettes per day for the duration of preganancy. I will use this as the treatment indicator, and the control group will be individuals who smoked 0 cigarettes per day for the duration of the pregnancy. The dataset encodes cigarette usage for each trimester of pregnancy differently, so I gather from each variable.<jupyter_code># First, drop anyone for whom we don't have smoking data:
df = df[df['cig_1'] != 99]
df = df[df['cig_2'] != 99]
df = df[df['cig_3'] != 99]

df['smokes_10'] = np.where(((df['cig_1'] >= 10) & (df['cig_2'] >=10) & (df['cig_3'] >= 10)), 1, 0)
df['nonsmoker'] = np.where(((df['cig_1'] == 0) & (df['cig_2'] == 0) & (df['cig_3'] == 0)), 1, 0)

# if you're not smoking 10 and you're not a nonsmoker then these two values will both be 0. We remove where they're equal. 
df = (df[df['nonsmoker'] != df['smokes_10']])<jupyter_output><empty_output><jupyter_text>I remove many observational covariates from the original dataset. The variables names here are the exact variable names in the original dataset, so they can be searched for in the [User Guide](http://data.nber.org/natality/2010/natl2010.pdf). More details on the meaning are available there. Briefly, I summarize the variables below. Note that some of these variables are recoded and could be considered "binned" continuous variables. The `DAME-FLAME` package is only for discrete observational datasets, but there are acceptable forms of data binning for some research questions. In this case, we assume binning these variables is acceptable because of prior research that relies on binned data such as [Kondracki (2020)](https://link.springer.com/content/pdf/10.1186/s12884-020-02981-1.pdf). For example, data that is binned include fathers age into fixed width bins in intervals of 5 years, and mother's education into bins of degree obtained rather than years of study. There is substantial research precedence that uses educational degree obtained as a variable on outcome rather than years of study. | Variable Name | Meaning || ------------- | ------- || fagerec11 | Fathers Age Recoded || meduc | Mothers Education || dob_wk | Weekday || mager9 | Mother's Age Recode 9 || fbrace | Father's Race || mar | Mother's marital status || rf_diab | Pre-pregnancy diabetes || rf_cesar | Previous Cesarean Deliveries || tbo_rec | Total Birth Order || rf_phyp | Prepregnancy hypertension || urf_chyper | chronic hypertension || lbo_rec | live birth order || rf_ppterm | previous preterm birth || sex | Sex of Infant || mbrace | Mother's bridged race || bfacil3 | Birth Place (home, hospital) || dbwt | Infant's birthweight |<jupyter_code>df_trunc = df.loc[:, df.columns.intersection(['fagerec11', 'meduc', 'dob_wk', 'mager9',
                                    'fbrace', 'mar','rf_diab', 'rf_cesar', 
                                  'tbo_rec', 'rf_phyp', 'urf_chyper', 'lbo_rec',
                                  'rf_ppterm', 'sex', 'mbrace', 'bfacil3', 'smokes_10', 'dbwt'])]
# Missing values are encoded differently throughout the dataset (see user guide), but I aim to encode them all with the 999 character

df_trunc.loc[df_trunc['bfacil3'] == 9, 'bfacil3'] = np.nan
df_trunc.loc[df_trunc['mar'] == 9, 'mar'] = np.nan
df_trunc.loc[df_trunc['meduc'] == 9, 'meduc'] = np.nan
df_trunc.loc[df_trunc['fagerec11'] == 11, 'fagerec11'] = np.nan
df_trunc.loc[df_trunc['fbrace'] == 99, 'fbrace'] = np.nan
df_trunc.loc[df_trunc['lbo_rec'] == 9, 'lbo_rec'] = np.nan
df_trunc.loc[df_trunc['tbo_rec'] == 9, 'tbo_rec'] = np.nan
df_trunc.loc[df_trunc['urf_chyper'] == 9, 'urf_chyper'] = np.nan

# Need to transform all the "N" and "Y" to 0/1
d = {"N": 0, "Y": 1, "U": np.nan}
df_trunc['rf_diab'] = df_trunc['rf_diab'].map(d)
df_trunc['rf_phyp'] = df_trunc['rf_phyp'].map(d)
df_trunc['rf_ppterm'] = df_trunc['rf_ppterm'].map(d)
df_trunc['rf_cesar'] = df_trunc['rf_cesar'].map(d)

# Transforming Gender to boolean
d = {"F":1, "M": 0}
df_trunc['sex'] = df_trunc['sex'].map(d)

# Make sure all clear for input to DAME-FLAME package
df_trunc = df_trunc.dropna()
df_trunc = df_trunc.astype(int)
df_trunc.head()
# This is the number of smokers and non-smokers in the dataset:
df_trunc['smokes_10'].value_counts()<jupyter_output><empty_output><jupyter_text>Create treatment/control matches with the DAME-FLAME package<jupyter_code># extreme smoking is the treatment column, birthweight is the outcome
model_flame = dame_flame.matching.FLAME(repeats=False, verbose=3, adaptive_weights='decisiontreeCV')
model_flame.fit(holdout_data=df_trunc, treatment_column_name='smokes_10', outcome_column_name='dbwt')
result_flame = model_flame.predict(df_trunc)<jupyter_output>Iteration number:  1
	Number of matched groups formed in total:  2418
	Unmatched treated units:  2045 out of a total of  5239 treated units
	Unmatched control units:  139525 out of a total of  168897 control units
	Predictive error of covariates chosen this iteration:  0
	Number of matches made in this iteration:  32566
	Number of matches made so far:  32566
	In this iteration, the covariates dropped are:  set()
Iteration number:  2
	Number of matched groups formed in total:  3216
	Unmatched treated units:  1174 out of a total of  5239 treated units
	Unmatched control units:  136909 out of a total of  168897 control units
	Predictive error of covariates chosen this iteration:  622376.8987389146
	Number of matches made in this iteration:  3487
	Number of matches made so far:  36053
	In this iteration, the covariates dropped are:  dob_wk
Iteration number:  3
	Number of matched groups formed in total:  3707
	Unmatched treated units:  641 out of a total of  5239 treated units
	Unmatched co[...]<jupyter_text>Plot and Discuss Outcome The covariate dropping order, shown above using the `verbose=3` parameter, is something we can try to use to interpret covariate importance. The FLAME algorithm learns the importance of each covariate on the outcome when choosing the best covariate to drop. Thus, the earlier dropped covariates are less relevant to the outcome. In this case, FLAME would lead us to believe that some factors, such as mother's education and the day of the week of the infant birth, are less predictive of an infant's birth weight than other factors, such as the sex of the infant.<jupyter_code>dame_flame.utils.post_processing.ATE(model_flame)<jupyter_output><empty_output><jupyter_text>So, as we can see, the ATE is highly negative, implying that maternal extreme smoking causes an infant birthweight to be lower than not-smoking by this many grams.<jupyter_code>group_size_treated = []
group_size_overall = []
cate_of_group = []
for group in model_flame.units_per_group:
    
    # find len of just treated units
    df_mmg = df.loc[group]
    treated = df_mmg.loc[df_mmg["smokes_10"] == 1]
    
    group_size_treated.append(len(treated))
    group_size_overall.append(len(group))
    
    cate_of_group.append(dame_flame.utils.post_processing.CATE(model_flame, group[0]))
plt.scatter(group_size_treated, cate_of_group, alpha=0.25, edgecolors='b')
plt.axhline(y=0.0, color='r', linestyle='-')
plt.xlabel('Number of Treatment units in group', fontsize=12)
plt.ylabel('Estimated Treatment Effect of Group', fontsize=12)
plt.title("Visualizing CATE of matched groups from FLAME", fontsize=14)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09759817003431186, 'fraction_numerical': 0.030213495996950057, 'mean_word_length': 4.868568232662192, 'pattern_counts': {'":': 5, '<': 30, '<?xml version=': 0, '>': 35, 'https://': 5, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '47833', 'license': 'MIT', 'n_tokens_mistral': 3504, 'n_tokens_neox': 3122, 'n_words': 1244, 'path': 'examples/Natality_Experiment_For_CodeOcean.ipynb', 'repo_name': 'vittorioorlandi/DAME-FLAME-Python-Package', 'size': '10501'}