更多原始数据文档和JupyterNotebook
Github: https://github.com/JinnyR/Datacamp_DataScienceTrack_Python
Datacamp track: Data Scientist with Python - Course 22 (2)
Exercise
Setting up a train-test split in scikit-learn
Alright, you’ve been patient and awesome. It’s finally time to start training models!
The first step is to split the data into a training set and a test set. Some labels don’t occur very often, but we want to make sure that they appear in both the training and the test sets. We provide a function that will make sure at least min_count
examples of each label appear in each split: multilabel_train_test_split
.
Feel free to check out the full code for multilabel_train_test_split
here.
You’ll start with a simple model that uses just the numeric columns of your DataFrame when calling multilabel_train_test_split
. The data has been read into a DataFrame df
and a list consisting of just the numeric columns is available as NUMERIC_COLUMNS
.
Instruction
- Create a new DataFrame named
numeric_data_only
by applying the.fillna(-1000)
method to the numeric columns (available in the listNUMERIC_COLUMNS
) ofdf
. - Convert the labels (available in the list
LABELS
) to dummy variables. Save the result aslabel_dummies
. - In the call to
multilabel_train_test_split()
, set thesize
of your test set to be0.2
. Use aseed
of123
. - Fill in the
.info()
method calls forX_train
,X_test
,y_train
, andy_test
.
import pandas as pd
import numpy as np
from warnings import warn
from sklearn.feature_extraction.text import CountVectorizer
#### DEFINE SAMPLING UTILITIES
# First multilabel_sample, which is called by multilabel_train_test_split
def multilabel_sample(y, size=1000, min_count=5, seed=None):
try:
if (np.unique(y).astype(int) != np.array([0, 1])).all():
raise ValueError()
except (TypeError, ValueError):
raise ValueError('multilabel_sample only works with binary indicator matrices')
if (y.sum(axis=0) < min_count).any():
raise ValueError('Some classes do not have enough examples. Change min_count if necessary.')
if size <= 1:
size = np.floor(y.shape[0] * size)
if y.shape[1] * min_count > size:
msg = "Size less than number of columns * min_count, returning {} items instead of {}."
warn(msg.format(y.shape[1] * min_count, size))
size = y.shape[1] * min_count
rng = np.random.RandomState(seed if seed is not None else np.random.randint(1))
if isinstance(y, pd.DataFrame):
choices = y.index
y = y.values
else:
choices = np.arange(y.shape[0])
sample_idxs = np.array([], dtype=choices.dtype)
# first, guarantee > min_count of each label
for j in range(y.shape[1]):
label_choices = choices[y[:, j] == 1]
label_idxs_sampled = rng.choice(label_choices, size=min_count, replace=False)
sample_idxs = np.concatenate([label_idxs_sampled, sample_idxs])
sample_idxs = np.unique(sample_idxs)
# now that we have at least min_count of each, we can just random sample
sample_count = size - sample_idxs.shape[0]
# get sample_count indices from remaining choices
remaining_choices = np.setdiff1d(choices, sample_idxs)
remaining_sampled = rng.choice(remaining_choices, size=sample_count, replace=False)
return np.concatenate([sample_idxs, remaining_sampled])
# Now define multilabel_train_test_split to be used below
def multilabel_train_test_split(X, Y, size, min_count=5, seed=None):
index = Y.index if isinstance(Y, pd.DataFrame) else np.arange(Y.shape[0])
test_set_idxs = multilabel_sample(Y, size=size, min_count=min_count, seed=seed)
train_set_idxs = np.setdiff1d(index, test_set_idxs)
test_set_mask = index.isin(test_set_idxs)
train_set_mask = ~test_set_mask
return (X[train_set_mask], X[test_set_mask], Y[train_set_mask], Y[test_set_mask])
#### ####
# Load data
df = pd.read_csv('https://s3.amazonaws.com/assets.datacamp.com/production/course_2533/datasets/TrainingSetSample.csv', index_col=0)
# Load LABELS and NUMERIC_COLUMNS lists
LABELS = ['Function',
'Use',
'Sharing',
'Reporting',
'Student_Type',
'Position_Type',
'Object_Type',
'Pre_K',
'Operating_Status']
NUMERIC_COLUMNS = ['FTE',
'Total'
]
# Convert object to category for LABELS
df[LABELS] = df[LABELS].apply(lambda x: x.astype('category'))
# Create the new DataFrame: numeric_data_only
numeric_data_only = df[NUMERIC_COLUMNS].fillna(-1000)
# Get labels and convert to dummy variables: label_dummies
label_dummies = pd.get_dummies(df[LABELS])
# Create training and test sets
X_train, X_test, y_train, y_test = multilabel_train_test_split(numeric_data_only,
label_dummies,
size=0.2,
seed=123)
# Print the info
print("X_train info:")
print(X_train.info())
print("\nX_test info:")
print(X_test.info())
print("\ny_train info:")
print(y_train.info())
print("\ny_test info:")
print(y_test.info())
X_train info:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1040 entries, 198 to 101861
Data columns (total 2 columns):
FTE 1040 non-null float64
Total 1040 non-null float64
dtypes: float64(2)
memory usage: 24.4 KB
None
X_test info:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 520 entries, 209 to 448628
Data columns (total 2 columns):
FTE 520 non-null float64
Total 520 non-null float64
dtypes: float64(2)
memory usage: 12.2 KB
None
y_train info:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1040 entries, 198 to 101861
Columns: 104 entries, Function_Aides Compensation to Operating_Status_PreK-12 Operating
dtypes: uint8(104)
memory usage: 113.8 KB
None
y_test info:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 520 entries, 209 to 448628
Columns: 104 entries, Function_Aides Compensation to Operating_Status_PreK-12 Operating
dtypes: uint8(104)
memory usage: 56.9 KB
None
Exercise
Training a model
With split data in hand, you’re only a few lines away from training a model.
In this exercise, you will import the logistic regression and one versus rest classifiers in order to fit a multi-class logistic regression model to the NUMERIC_COLUMNS
of your feature data.
Then you’ll test and print the accuracy with the .score()
method to see the results of training.
Before you train! Remember, we’re ultimately going to be using logloss to score our model, so don’t worry too much about the accuracy here. Keep in mind that you’re throwing away all of the text data in the dataset - that’s by far most of the data! So don’t get your hopes up for a killer performance just yet. We’re just interested in getting things up and running at the moment.
All data necessary to call multilabel_train_test_split()
has been loaded into the workspace.
Instruction
- Import
LogisticRegression
fromsklearn.linear_model
andOneVsRestClassifier
fromsklearn.multiclass
. - Instantiate the classifier
clf
by placingLogisticRegression()
insideOneVsRestClassifier()
. - Fit the classifier to the training data
X_train
andy_train
. - Compute and print the accuracy of the classifier using its
.score()
method, which accepts two arguments:X_test
andy_test
.
# Import classifiers
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsRestClassifier
# Create the DataFrame: numeric_data_only
numeric_data_only = df[NUMERIC_COLUMNS].fillna(-1000)
# Get labels and convert to dummy variables: label_dummies
label_dummies = pd.get_dummies(df[LABELS])
# Create training and test sets
X_train, X_test, y_train, y_test = multilabel_train_test_split(numeric_data_only,
label_dummies,
size=0.2,
seed=123)
# Instantiate the classifier: clf
clf = OneVsRestClassifier(LogisticRegression(solver="liblinear"))
# Fit the classifier to the training data
clf.fit(X_train, y_train)
# Print the accuracy
print("Accuracy: {}".format(clf.score(X_test, y_test)))
Accuracy: 0.0
Exercise
Use your model to predict values on holdout data
You’re ready to make some predictions! Remember, the train-test-split you’ve carried out so far is for model development. The original competition provides an additional test set, for which you’ll never actually see the correct labels. This is called the “holdout data.”
The point of the holdout data is to provide a fair test for machine learning competitions. If the labels aren’t known by anyone but DataCamp, DrivenData, or whoever is hosting the competition, you can be sure that no one submits a mere copy of labels to artificially pump up the performance on their model.
Remember that the original goal is to predict the probability of each label. In this exercise you’ll do just that by using the .predict_proba()
method on your trained model.
First, however, you’ll need to load the holdout data, which is available in the workspace as the file HoldoutData.csv
.
Instruction
- Read
HoldoutData.csv
into a DataFrame calledholdout
. Specify the keyword argumentindex_col=0
in your call toread_csv()
. - Generate predictions using
.predict_proba()
on the numeric columns (available in theNUMERIC_COLUMNS
list) ofholdout
. Make sure to fill in missing values with-1000
!
# Load path to holdout
PATH_TO_HOLDOUT_DATA = 'https://s3.amazonaws.com/assets.datacamp.com/production/course_2826/datasets/TestSetSample.csv'
PATH_TO_HOLDOUT_LABELS = 'https://s3.amazonaws.com/assets.datacamp.com/production/course_2826/datasets/TestSetLabelsSample.csv'
fn = 'https://s3.amazonaws.com/assets.datacamp.com/production/course_2826/datasets/TestSetSample.csv'
from urllib.request import urlretrieve
urlretrieve(fn, 'HoldoutData.csv')
('HoldoutData.csv', <http.client.HTTPMessage at 0x11121dc50>)
# Instantiate the classifier: clf
clf = OneVsRestClassifier(LogisticRegression(solver='liblinear'))
# Fit it to the training data
clf.fit(X_train, y_train)
# Load the holdout data: holdout
holdout = pd.read_csv('HoldoutData.csv',index_col=0)
# Generate predictions: predictions
predictions = clf.predict_proba(holdout[NUMERIC_COLUMNS].fillna(-1000))
Exercise
Writing out your results to a csv for submission
At last, you’re ready to submit some predictions for scoring. In this exercise, you’ll write your predictions to a .csv
using the .to_csv()
method on a pandas DataFrame. Then you’ll evaluate your performance according to the LogLoss metric discussed earlier!
You’ll need to make sure your submission obeys the correct format.
To do this, you’ll use your predictions
values to create a new DataFrame, prediction_df
.
Interpreting LogLoss & Beating the Benchmark:
When interpreting your log loss score, keep in mind that the score will change based on the number of samples tested. To get a sense of how this very basic model performs, compare your score to the DrivenData benchmark model performance: 2.0455, which merely submitted uniform probabilities for each class.
Remember, the lower the log loss the better. Is your model’s log loss lower than 2.0455?
Instruction
- Create the
prediction_df
DataFrame by specifying the following arguments to the provided parameterspd.DataFrame()
:pd.get_dummies(df[LABELS]).columns
.holdout.index
.predictions
.
- Save
prediction_df
to a csv file called'predictions.csv'
using the.to_csv()
method. - Submit the predictions for scoring by using the
score_submission()
function withpred_path
set to'predictions.csv'
.
# Load path to pred
PATH_TO_PREDICTIONS = "predictions.csv"
# Load path to holdout
PATH_TO_HOLDOUT_DATA = "https://s3.amazonaws.com/assets.datacamp.com/production/course_2826/datasets/TestSetSample.csv"
PATH_TO_HOLDOUT_LABELS = "https://s3.amazonaws.com/assets.datacamp.com/production/course_2826/datasets/TestSetLabelsSample.csv"
# SCORING UTILITIES
BOX_PLOTS_COLUMN_INDICES = [range(37),
range(37,48),
range(48,51),
range(51,76),
range(76,79),
range(79,82),
range(82,87),
range(87,96),
range(96,104)]
def _multi_multi_log_loss(predicted,
actual,
class_column_indices=BOX_PLOTS_COLUMN_INDICES,
eps=1e-15):
class_scores = np.ones(len(class_column_indices), dtype=np.float64)
# calculate log loss for each set of columns that belong to a class:
for k, this_class_indices in enumerate(class_column_indices):
# get just the columns for this class
preds_k = predicted[:, this_class_indices].astype(np.float64)
# normalize so probabilities sum to one (unless sum is zero, then we clip)
preds_k /= np.clip(preds_k.sum(axis=1).reshape(-1, 1), eps, np.inf)
actual_k = actual[:, this_class_indices]
# shrink predictions so
y_hats = np.clip(preds_k, eps, 1 - eps)
sum_logs = np.sum(actual_k * np.log(y_hats))
class_scores[k] = (-1.0 / actual.shape[0]) * sum_logs
return np.average(class_scores)
def score_submission(pred_path=PATH_TO_PREDICTIONS, holdout_path=PATH_TO_HOLDOUT_LABELS):
# this happens on the backend to get the score
holdout_labels = pd.get_dummies(
pd.read_csv(holdout_path, index_col=0)
.apply(lambda x: x.astype('category'), axis=0)
)
preds = pd.read_csv(pred_path, index_col=0)
# make sure that format is correct
assert (preds.columns == holdout_labels.columns).all()
assert (preds.index == holdout_labels.index).all()
return _multi_multi_log_loss(preds.values, holdout_labels.values)
# Load holdout data
holdout = pd.read_csv(PATH_TO_HOLDOUT_DATA, index_col=0)
# Generate predictions: predictions
predictions = clf.predict_proba(holdout[NUMERIC_COLUMNS].fillna(-1000))
# Format predictions in DataFrame: prediction_df
prediction_df = pd.DataFrame(columns=pd.get_dummies(df[LABELS]).columns,
index=holdout.index,
data=predictions)
# Save prediction_df to csv
prediction_df.to_csv('predictions.csv')
# Submit the predictions for scoring: score
score = score_submission(pred_path='predictions.csv')
# Print score
print('Your model, trained with numeric data only, yields logloss score: {}'.format(score))
Your model, trained with numeric data only, yields logloss score: 1.235598965019296
Exercise
Creating a bag-of-words in scikit-learn
In this exercise, you’ll study the effects of tokenizing in different ways by comparing the bag-of-words representations resulting from different token patterns.
You will focus on one feature only, the Position_Extra
column, which describes any additional information not captured by the Position_Type
label.
For example, in the Shell you can check out the budget item in row 8960 of the data using df.loc[8960]
. Looking at the output reveals that this Object_Description
is overtime pay. For who? The Position Type is merely “other”, but the Position Extra elaborates: “BUS DRIVER”. Explore the column further to see more instances. It has a lot of NaN values.
Your task is to turn the raw text in this column into a bag-of-words representation by creating tokens that contain onlyalphanumeric characters.
For comparison purposes, the first 15 tokens of vec_basic
, which splits df.Position_Extra
into tokens when it encounters only whitespace characters, have been printed along with the length of the representation.
Instruction
- Import
CountVectorizer
fromsklearn.feature_extraction.text
. - Fill missing values in
df.Position_Extra
using.fillna('')
to replace NaNs with empty strings. Specify the additional keyword argumentinplace=True
so that you don’t have to assign the result back todf
. - Instantiate the
CountVectorizer
asvec_alphanumeric
by specifying thetoken_pattern
to beTOKENS_ALPHANUMERIC
. - Fit
vec_alphanumeric
todf.Position_Extra
. - Hit ‘Submit Answer’ to see the
len
of the fitted representation as well as the first 15 elements, and compare tovec_basic
.
# Import CountVectorizer
from sklearn.feature_extraction.text import CountVectorizer
# Create the token pattern: TOKENS_ALPHANUMERIC
TOKENS_ALPHANUMERIC = '[A-Za-z0-9]+(?=\\s+)'
# Fill missing values in df.Position_Extra
df.Position_Extra.fillna('', inplace=True)
# Instantiate the CountVectorizer: vec_alphanumeric
vec_alphanumeric = CountVectorizer(token_pattern=TOKENS_ALPHANUMERIC)
# Fit to the data
vec_alphanumeric.fit(df.Position_Extra)
# Print the number of tokens and first 15 tokens
msg = "There are {} tokens in Position_Extra if we split on non-alpha numeric"
print(msg.format(len(vec_alphanumeric.get_feature_names())))
print(vec_alphanumeric.get_feature_names()[:15])
There are 123 tokens in Position_Extra if we split on non-alpha numeric
['1st', '2nd', '3rd', 'a', 'ab', 'additional', 'adm', 'administrative', 'and', 'any', 'art', 'assessment', 'assistant', 'asst', 'athletic']
Exercise
Combining text columns for tokenization
In order to get a bag-of-words representation for all of the text data in our DataFrame, you must first convert the text data in each row of the DataFrame into a single string.
In the previous exercise, this wasn’t necessary because you only looked at one column of data, so each row was already just a single string. CountVectorizer
expects each row to just be a single string, so in order to use all of the text columns, you’ll need a method to turn a list of strings into a single string.
In this exercise, you’ll complete the function definition combine_text_columns()
. When completed, this function will convert all training text data in your DataFrame to a single string per row that can be passed to the vectorizer object and made into a bag-of-words using the .fit_transform()
method.
Note that the function uses NUMERIC_COLUMNS
and LABELS
to determine which columns to drop. These lists have been loaded into the workspace.
Instruction
- Use the
.drop()
method ondata_frame
withto_drop
andaxis=
as arguments to drop the non-text data. Save the result astext_data
. - Fill in missing values (inplace) in
text_data
with blanks (""
), using the.fillna()
method. - Complete the
.apply()
method by writing a lambda function that uses the.join()
method to join all the items in a row with a space in between.
# Define combine_text_columns()
def combine_text_columns(data_frame, to_drop=NUMERIC_COLUMNS + LABELS):
""" converts all text in each row of data_frame to single vector """
# Drop non-text columns that are in the df
to_drop = set(to_drop) & set(data_frame.columns.tolist())
text_data = data_frame.drop(to_drop, axis=1)
# Replace nans with blanks
text_data.fillna('', inplace=True)
# Join all text items in a row that have a space in between
return text_data.apply(lambda x: " ".join(x), axis=1)
Exercise
What’s in a token?
Now you will use combine_text_columns
to convert all training text data in your DataFrame to a single vector that can be passed to the vectorizer object and made into a bag-of-words using the .fit_transform()
method.
You’ll compare the effect of tokenizing using any non-whitespace characters as a token and using only alphanumeric characters as a token
Instruction
- Import
CountVectorizer
fromsklearn.feature_extraction.text
. - Instantiate
vec_basic
andvec_alphanumeric
using, respectively, theTOKENS_BASIC
andTOKENS_ALPHANUMERIC
patterns. - Create the text vector by using the
combine_text_columns()
function ondf
. - Using the
.fit_transform()
method withtext_vector
, fit and transform firstvec_basic
and thenvec_alphanumeric
. Print the number of tokens they contain.
# Import the CountVectorizer
from sklearn.feature_extraction.text import CountVectorizer
# Create the basic token pattern
TOKENS_BASIC = '\\S+(?=\\s+)'
# Create the alphanumeric token pattern
TOKENS_ALPHANUMERIC = '[A-Za-z0-9]+(?=\\s+)'
# Instantiate basic CountVectorizer: vec_basic
vec_basic = CountVectorizer(token_pattern=TOKENS_BASIC)
# Instantiate alphanumeric CountVectorizer: vec_alphanumeric
vec_alphanumeric = CountVectorizer(token_pattern=TOKENS_ALPHANUMERIC)
# Create the text vector
text_vector = combine_text_columns(df)
# Fit and transform vec_basic
vec_basic.fit_transform(text_vector, vec_basic)
# Print number of tokens of vec_basic
print("There are {} tokens in the dataset".format(len(vec_basic.get_feature_names())))
# Fit and transform vec_alphanumeric
vec_alphanumeric.fit_transform(text_vector, vec_alphanumeric)
# Print number of tokens of vec_alphanumeric
print("There are {} alpha-numeric tokens in the dataset".format(len(vec_alphanumeric.get_feature_names())))
There are 1404 tokens in the dataset
There are 1117 alpha-numeric tokens in the dataset