<jupyter_start><jupyter_text>Law School Admissions Data - Analysis Code to analyze self-reported law school admissions data from Law School Numbers and predict admissions results.<jupyter_code>import pandas as pd
pd.set_option('display.max_colwidth', -1)
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)

import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import numpy as np
from operator import itemgetter

from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_absolute_error
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
from sklearn import preprocessing<jupyter_output><empty_output><jupyter_text>Import raw data<jupyter_code>df_law = pd.read_csv(r'C:\Users\Jenny\lawschool\lawschool_clean.csv', index_col=0)
df_law.head()<jupyter_output>C:\Users\Jenny\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py:2728: DtypeWarning: Columns (19) have mixed types. Specify dtype option on import or set low_memory=False.
  interactivity=interactivity, compiler=compiler, result=result)<jupyter_text>Exploratory data analysis Look at distribution, check for anomalies, and count null values.<jupyter_code># % of null values for each column
# 'sent', 'decision', and 'years_out' have a lot of null values - they will likely be excluded from analysis. 
100 * df_law.isnull().sum() / len(df_law)
# % of zero values for each column
# 'early_decision', 'fee_waiver', 'nontraditional', 'international', 'lgbt', 'tfa', and 'military' are mostly zero, so they will be dropped from analysis.
100 * df_law[df_law == 0].count() / len(df_law)
# Distribution of 'accepted' (whether an applicant was accepted to the school)
plt.hist(df_law['accepted'])
# Distribution of lsat scores
plt.hist(df_law['lsat'], bins=50)
plt.xlim(110,)
plt.title('Distribution of LSAT Scores', y=1.05)
plt.xlabel('LSAT Score')
plt.ylabel('Frequency')
plt.savefig('lsat-hist.jpg', dpi=150, bbox_inches = "tight")
# % of applicants admitted by LSAT Score
df_lsat = df_law.loc[:, ['lsat', 'accepted']]
lsat_bins = np.linspace(140, 180, 20)
df_lsat['lsat_binned'] = pd.cut(df_lsat['lsat'], bins = lsat_bins)
lsat_group = df_lsat.groupby('lsat_binned').mean()

x_axis = range(len(lsat_bins)-1)
x_label = [int(x) for x in lsat_bins]
plt.plot(x_axis, lsat_group['accepted'] *100)

plt.xticks(x_axis, x_label, rotation=90)
plt.title('% of applicants admitted by LSAT Score', y=1.05)
plt.xlabel('LSAT Score')
plt.ylabel('% admitted')
plt.savefig('lsat-admissions.jpg', dpi=150, bbox_inches = "tight")
# Distribution of GPAs
plt.hist(df_law['gpa'], bins=50)
plt.xlim(1.5,)
plt.title('Distribution of GPAs', y=1.05)
plt.xlabel('GPA')
plt.ylabel('Frequency')
plt.savefig('gpa-hist.jpg', dpi=150, bbox_inches = "tight")
df_gpa = df_law.loc[:, ['gpa', 'accepted']]
gpa_bins = np.linspace(2, 4, 20)
df_gpa['gpa_binned'] = pd.cut(df_gpa['gpa'], bins = gpa_bins)
gpa_group = df_gpa.groupby('gpa_binned').mean()

x_axis = range(len(gpa_bins)-1)
x_label = [x.round(1) for x in gpa_bins]
plt.plot(x_axis, gpa_group['accepted'] *100)

plt.xticks(x_axis, x_label, rotation=90)
plt.title('% of applicants admitted by GPA', y=1.05)
plt.xlabel('GPA')
plt.ylabel('% admitted')
plt.savefig('gpa-admissions.jpg', dpi=150, bbox_inches = "tight")
# Distribution of races
df_law.groupby('race')['race'].count().sort_values(ascending=False).plot.bar()
# Distribution of majors
df_law.groupby('major')['major'].count().sort_values(ascending=False).plot.bar()
# Distribution of 'school_type' which the applicant attended for undergrad
df_law.groupby('school_type')['school_type'].count().sort_values(ascending=False).plot.bar()
# Distribution of schools by applicant_count
group_school_count = df_law.groupby('school')['school'].count().sort_values(ascending=False)
dict_applicant_count = dict(group_school_count)
df_law['applicant_count'] = df_law['school'].map(dict_applicant_count)

plt.hist(df_law['applicant_count'], bins=20)
plt.title('Distribution of Schools by Applicant Count', y=1.05)
plt.xlabel('Applicant count')
plt.ylabel('Frequency')
plt.savefig('applicant-count.jpg', dpi=150, bbox_inches = "tight")<jupyter_output><empty_output><jupyter_text>Feature engineering Select and create features to be used in the model to predict admissions outcomes.<jupyter_code># Bucket schools by applicant_count
# 1 is for schools with over 8000 applicants, 2 is for schools with other 4000, and 3 is for other schools
df_law['applicant_count_level'] = df_law['applicant_count'].apply(lambda x: 1 if x>8000 else 2 if x>4000 else 3)
# Create 'in_state' column for applicants applying to a school that is in-state
df_law['in_state'] = np.where((df_law['applicant_state'] == df_law['school_state']), 1, 0)
# Create a weighted LSAT & GPA score (lsat_gpa_weighted) with each feature equally weighted on a 100-point scale
df_law['lsat_gpa_weighted'] = df_law.apply(lambda x: x['lsat']/180*50 + x['gpa']/4.4*50, axis=1)
# Select features from df_law and one hot encode categorical data (school, race)
df_law_features = pd.DataFrame(df_law[['gpa', 'lsat', 'lsat_gpa_weighted', 'in_state', 'applicant_count_level']])
df_race = pd.get_dummies(df_law['race'], prefix='race')
df_school = pd.get_dummies(df_law['school'], prefix='school')
# Create X with features that will be used to predict the target
X = pd.concat([df_law_features, df_race, df_school], axis=1)
X.head()
# Correlation matrix to show the correlation between features

corr = X[['gpa', 'lsat', 'lsat_gpa_weighted', 'in_state', 'applicant_count_level', 'race_asian', 'race_black', 'race_hispanic', 'race_other', 'race_other_urm']].corr()
cmap = sns.diverging_palette( 220 , 10 , as_cmap = True )
sns.heatmap(corr, cmap=cmap)<jupyter_output><empty_output><jupyter_text>Test models<jupyter_code># Create y with the prediction target 'accepted' (whether an applicant was accepted by a school)
y = df_law['accepted']
# Split data into training and validation data
train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)
# Define the function model_test to run all models and return the name of the model and its MAE

def model_test(models):
    list_model_mae = []
    
    for model_name, model_type in models.items():
        model = model_type.fit(train_X, train_y)
        prediction = model.predict(val_X)
        mae = mean_absolute_error(val_y, prediction)
        list_model_mae.append(tuple((model_name, mae)))
    
    return sorted(list_model_mae, key=itemgetter(1))
# Create dict_models with the models to be tested

dict_models = {
    'Random Forest': RandomForestClassifier(random_state=1),
    'Decision Tree': DecisionTreeClassifier(),
    'Logistic Regression': LogisticRegression(),
    'KNN': KNeighborsClassifier(n_neighbors=3),
    'Gaussian Naive Bayes': GaussianNB()
}
# Run model_test
model_test(dict_models)<jupyter_output><empty_output><jupyter_text>model_test (above) shows that logistic regression and random forest are the most accurate models for our data.<jupyter_code># Check feature importance for the Random Forest model
forest = ExtraTreesClassifier(random_state=0)
forest.fit(X, y)
importances = forest.feature_importances_
std = np.std([tree.feature_importances_ for tree in forest.estimators_],
             axis=0)
indices = np.argsort(importances)[::-1]

for f in range(X.shape[1]):
    print(f"{f + 1}. {X.columns[indices[f]]} ({importances[indices[f]]})")<jupyter_output>1. lsat_gpa_weighted (0.29669533338789)
2. lsat (0.25664937079274425)
3. gpa (0.21712584387273245)
4. applicant_count_level (0.02234175544498992)
5. school_stanford (0.008886860096007119)
6. school_yale (0.006307835950748718)
7. school_harvard (0.0041875311062217245)
8. race_white (0.0036695252210099992)
9. in_state (0.0036535301550632794)
10. race_other (0.0035809941206745967)
11. school_penn (0.002995845233650128)
12. school_berkeley (0.002879338418400899)
13. race_black (0.002809416098792088)
14. school_uva (0.002465089566862744)
15. school_gw (0.0023358220692482573)
16. school_columbia (0.002286183576744325)
17. race_hispanic (0.002240012449045281)
18. school_gulc (0.002125253586657087)
19. school_miami (0.0017831869194280948)
20. school_minnesota (0.0017373802346364003)
21. school_northwestern (0.0016754615892855633)
22. school_georgia (0.0016556987481065044)
23. school_cardozo (0.0016242867272569087)
24. school_sandiego (0.0016060263791860929)
25. race_asian (0.001596154286217023[...]<jupyter_text>Fine-tune models by adjusting parameters<jupyter_code># Create a function to run logistic regression over a list of values for parameter C (inverse of regularization) optimize accuracy

def logit(c):
    dict_logit_mae = {}    
    
    for i in c:
        model = LogisticRegression(C=i).fit(train_X, train_y)
        prediction = model.predict(val_X)
        mae = mean_absolute_error(val_y, prediction)
        dict_logit_mae[i] = mae
    
    return dict_logit_mae
# Adjusting C appears to have little impact on accuracy
list_c = range(1, 100, 10)
logit(list_c)
# Create a function to run Random Forest over a list of values for n_estimators (number of trees) to optimize accuracy

def forest_est(n):
    dict_forest_mae = {}    
    
    for i in n:
        model = RandomForestClassifier(random_state=1, n_estimators=i).fit(train_X, train_y)
        prediction = model.predict(val_X)
        mae = mean_absolute_error(val_y, prediction)
        dict_forest_mae[i] = mae
    
    return dict_forest_mae
# n_estimators as 100 appears to be a good value for our model.
# Values over 100 marginally reduce error but take much longer to run. 
list_n = range(10, 200, 20)
forest_est(list_n)
# Create a function to run Random Forest over a list of values for max_features to optimize accuracy

def forest_features(n):
    dict_forest_mae = {}
    
    for i in n:
        model = RandomForestClassifier(random_state=1, max_features=i).fit(train_X, train_y)
        prediction = model.predict(val_X)
        mae = mean_absolute_error(val_y, prediction)
        dict_forest_mae[i] = mae
    
    return dict_forest_mae
# max_features as 50 appears to be a good value for our model

list_max_features = range(10, 80, 10)
forest_features(list_max_features)
# Random Forest for optimized n_estimators and max_features

model = RandomForestClassifier(random_state=1, max_features=50, n_estimators=100).fit(train_X, train_y)
prediction = model.predict(val_X)
mae = mean_absolute_error(val_y, prediction)
mae<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11614391143911439, 'fraction_numerical': 0.06476014760147601, 'mean_word_length': 5.918315252074027, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '569651', 'license': 'MIT', 'n_tokens_mistral': 4372, 'n_tokens_neox': 3801, 'n_words': 942, 'path': 'law_school_admissions-master/lawschool_analysis.ipynb', 'repo_name': 'jonathanyxchen/ML-Data-to-Models-project', 'size': '10840'}