<jupyter_start><jupyter_text>Starbucks Capstone Challenge IntroductionThis data set contains simulated data that mimics customer behavior on the Starbucks rewards mobile app. Once every few days, Starbucks sends out an offer to users of the mobile app. An offer can be merely an advertisement for a drink or an actual offer such as a discount or BOGO (buy one get one free). Some users might not receive any offer during certain weeks. Not all users receive the same offer, and that is the challenge to solve with this data set.Your task is to combine transaction, demographic and offer data to determine which demographic groups respond best to which offer type. This data set is a simplified version of the real Starbucks app because the underlying simulator only has one product whereas Starbucks actually sells dozens of products.Every offer has a validity period before the offer expires. As an example, a BOGO offer might be valid for only 5 days. You'll see in the data set that informational offers have a validity period even though these ads are merely providing information about a product; for example, if an informational offer has 7 days of validity, you can assume the customer is feeling the influence of the offer for 7 days after receiving the advertisement.You'll be given transactional data showing user purchases made on the app including the timestamp of purchase and the amount of money spent on a purchase. This transactional data also has a record for each offer that a user receives as well as a record for when a user actually views the offer. There are also records for when a user completes an offer. Keep in mind as well that someone using the app might make a purchase through the app without having received an offer or seen an offer. ExampleTo give an example, a user could receive a discount offer buy 10 dollars get 2 off on Monday. The offer is valid for 10 days from receipt. If the customer accumulates at least 10 dollars in purchases during the validity period, the customer completes the offer.However, there are a few things to watch out for in this data set. Customers do not opt into the offers that they receive; in other words, a user can receive an offer, never actually view the offer, and still complete the offer. For example, a user might receive the "buy 10 dollars get 2 dollars off offer", but the user never opens the offer during the 10 day validity period. The customer spends 15 dollars during those ten days. There will be an offer completion record in the data set; however, the customer was not influenced by the offer because the customer never viewed the offer. CleaningThis makes data cleaning especially important and tricky.You'll also want to take into account that some demographic groups will make purchases even if they don't receive an offer. From a business perspective, if a customer is going to make a 10 dollar purchase without an offer anyway, you wouldn't want to send a buy 10 dollars get 2 dollars off offer. You'll want to try to assess what a certain demographic group will buy when not receiving any offers. Final AdviceBecause this is a capstone project, you are free to analyze the data any way you see fit. For example, you could build a machine learning model that predicts how much someone will spend based on demographics and offer type. Or you could build a model that predicts whether or not someone will respond to an offer. Or, you don't need to build a machine learning model at all. You could develop a set of heuristics that determine what offer you should send to each customer (i.e., 75 percent of women customers who were 35 years old responded to offer A vs 40 percent from the same demographic to offer B, so send offer A).  Data SetsThe data is contained in three files:* portfolio.json - containing offer ids and meta data about each offer (duration, type, etc.)* profile.json - demographic data for each customer* transcript.json - records for transactions, offers received, offers viewed, and offers completedHere is the schema and explanation of each variable in the files:**portfolio.json*** id (string) - offer id* offer_type (string) - type of offer ie BOGO, discount, informational* difficulty (int) - minimum required spend to complete an offer* reward (int) - reward given for completing an offer* duration (int) - time for offer to be open, in days* channels (list of strings)**profile.json*** age (int) - age of the customer * became_member_on (int) - date when customer created an app account* gender (str) - gender of the customer (note some entries contain 'O' for other rather than M or F)* id (str) - customer id* income (float) - customer's income**transcript.json*** event (str) - record description (ie transaction, offer received, offer viewed, etc.)* person (str) - customer id* time (int) - time in hours since start of test. The data begins at time t=0* value - (dict of strings) - either an offer id or transaction amount depending on the record**Note:** If you are using the workspace, you will need to go to the terminal and run the command `conda update pandas` before reading in the files. This is because the version of pandas in the workspace cannot read in the transcript.json file correctly, but the newest version of pandas can. You can access the termnal from the orange icon in the top left of this notebook.  You can see how to access the terminal and how the install works using the two images below.  First you need to access the terminal:Then you will want to run the above command:Finally, when you enter back into the notebook (use the jupyter icon again), you should be able to run the below cell without any errors. In this post, I will analyze the Starbucks Dataset using the CRISP-DM Process. I will aim to answer a few business questions, by exploring the dataset using Data Visualization, and Modeling. The questions that I am interested in exploring are:1. How does Gender influence how much someone spends at Starbucks? Do men spend more than women, or the other way around?2. How many people view and complete offers? And how many people complete offers without opening them first?3. What are the attributes that most contribute to someone's spending habits at Starbucks?These questions help us with a *Business Understanding* of the domain. Let's start by first importing the required packages, and loading the datasets.<jupyter_code>import pandas as pd
import numpy as np
import math
import json
import seaborn as sns
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split;
from sklearn.preprocessing import StandardScaler;
from sklearn.svm import SVR;
from sklearn.model_selection import GridSearchCV;
from sklearn.metrics import r2_score;
from sklearn.ensemble import RandomForestRegressor;

%matplotlib inline

# read in the json files
portfolio = pd.read_json('data/portfolio.json', orient='records', lines=True)
profile = pd.read_json('data/profile.json', orient='records', lines=True)
transcript = pd.read_json('data/transcript.json', orient='records', lines=True)<jupyter_output><empty_output><jupyter_text>Exploring the DataWe'll look at these datasets by printing out the first few rows of each.<jupyter_code>portfolio.head()
profile.head()
transcript.head()<jupyter_output><empty_output><jupyter_text>We'll now visualize these datasets by plotting correlation plots in Portfolio and Profile.<jupyter_code>sns.pairplot(portfolio, hue='offer_type')<jupyter_output><empty_output><jupyter_text>We can see that **Discount** offers last the longest, and **Bogo** offers have the highest reward.<jupyter_code>sns.pairplot(profile.dropna(), hue='gender')<jupyter_output><empty_output><jupyter_text>There is a lot of data for the Profiles, and we can see some interesting trends from it. For instance, in terms of **age**, most female purchasers tend to be younger than most male purchasers.To explore the Transcript dataset, I'll look at the distribution of the types of offers that people respond to. I will plot the channels corresponding to the offers, and the type of offer they are.<jupyter_code># Replace missing offer id's with 0
all_offers = transcript.value.apply(lambda x: x['offer id'] if 'offer id' in x else 0).values
all_offers = all_offers[all_offers != 0];
portfolio_group = portfolio.groupby('id')[['offer_type', 'channels']];

# Flatten the channel names into an array
all_channels = [portfolio_group.get_group(ide).values[0][1] for ide in all_offers];
all_channels = [row for subrow in all_channels for row in subrow]

# Flatten the offer types into an array
all_types = [portfolio_group.get_group(ide).values[0][0] for ide in all_offers];
del all_offers, portfolio_group
plt.rcParams["figure.figsize"] = (10,4)

fig, (ax1, ax2) = plt.subplots(ncols = 2, sharey = True)

sns.countplot(all_channels, ax=ax1)
sns.countplot(all_types, ax=ax2)<jupyter_output><empty_output><jupyter_text>We see with these distributions that the most offers are sent through **Email** and **Mobile**. **Informational** offers are much more uncommon than the other types of offers as well. I'd like to explore the data further by filtering out only transactions that have been *viewed* and *completed*.<jupyter_code>del all_types, all_channels;<jupyter_output><empty_output><jupyter_text>Preparing the dataAs we can already see through the initial exploration, there are a lot of areas that need to be cleaned. This will include formatting the text columns, and removing missing values.We'll start with the **Portfolio** dataset. We see that **channels** are grouped as list values. I will separate the individual values and dummy-fy them. The same will be done for **offer types**. We'll then drop the original columns and append the newer one-hot encoded columns.<jupyter_code># Dummy-fy channel attributes
portfolio_channels = portfolio['channels'].apply(lambda x: ' '.join(x)).str.get_dummies(' ');
portfolio_channels.columns = ['channel_' + col for col in portfolio_channels.columns];

# Dummy-fy offer type attributes
portfolio_offertype = portfolio['offer_type'].str.get_dummies()
portfolio_offertype.columns = ['offer_' + col for col in portfolio_offertype.columns];
# Add dummy columns and drop existing
portfolio = pd.concat([portfolio, portfolio_channels, portfolio_offertype], axis=1);
portfolio = portfolio.drop(['channels', 'offer_type'], axis=1);
portfolio.head()
del portfolio_channels, portfolio_offertype
portfolio.isna().sum().sum()<jupyter_output><empty_output><jupyter_text>The above sample shows our final cleaned **Portfolio** dataset. The above call also shows that there are no missing values to be removed.Next, we look at the **Profile** dataset. Let's start by looking at missing value counts, and dropping these rows.<jupyter_code>print('# missing values of income, gender:')
profile['income'].isna().sum(), profile['gender'].isna().sum()
profile = profile.dropna(axis=0, subset=['gender', 'income']);<jupyter_output><empty_output><jupyter_text>The **Profile** dataset also has a column for membership data, **became_member_on**. I will separate out the date strings into *Year*, *Month*, and *Day*, and keep them as integer values. For **gender**, we will dummy-fy the values into separate one-hot-encodings.<jupyter_code>profile_gender = profile['gender'].str.get_dummies()
profile_gender.columns = ['gender_' + col for col in profile_gender.columns];
# Separate date attributes into year, month, and day, converting to integers.
profile_date = profile['became_member_on'];

profile_year = profile_date.apply(lambda d: str(d)).str[0:4].astype('int').rename('member_year');
profile_month = profile_date.apply(lambda d: str(d)).str[4:6].astype('int').rename('member_month');
profile_day = profile_date.apply(lambda d: str(d)).str[6:8].astype('int').rename('member_day');
profile = pd.concat([profile, profile_gender, profile_year, profile_month, profile_day], axis=1);
profile = profile.drop(['became_member_on', 'gender'], axis=1);
del profile_gender, profile_year, profile_month, profile_day
profile.head()<jupyter_output><empty_output><jupyter_text>The **Transcript** dataset is bigger and has more required cleaning. The column **value** is stored as a dictionary. The values in the dictionaries are inconsistent, with both `offer_id` and `offer id` stored. Once we clean those, they can be converted into columns. The **event** column will also be dummy-fied, and column names standardized.<jupyter_code>transcript_event = transcript['event'].str.get_dummies();
transcript_event.columns = ['event_' + '_'.join(col.split(' ')) for col in transcript_event.columns];
# standardize "offer id" column names
def transcript_value_clean(x_dict):
    if 'offer id' in x_dict:
        x_dict['offer_id'] = x_dict['offer id'];
        del x_dict['offer id'];
    return x_dict;

transcript_values = transcript['value'].apply(lambda x: transcript_value_clean(x));
transcript_values = pd.DataFrame(list(transcript_values.values));<jupyter_output><empty_output><jupyter_text>Amount values are stored in both **reward** and **amount** columns, and they distinguish different types of transactions. Instead of having both, we will merge both into a single column, and use new columns *is_reward* and *is_amount* for distinguishing them. Finally, if there is an **offer_id** associated with a transaction, *has_offer* will store it, and null values will be replaced with a 0.<jupyter_code>transcript_values['is_reward'] = transcript_values['reward'].apply(lambda x: int(not np.isnan(x)));
# merge amount and reward columns
transcript_values['is_amount'] = transcript_values['amount'].apply(lambda x: int(not np.isnan(x)));
transcript_values['amount'] = transcript_values[['amount', 'reward']].apply(lambda x: x[0] if np.isnan(x[1]) else x[1], axis=1)
# filling null offer ids with "0"
transcript_values['has_offer'] = transcript_values['offer_id'].apply(lambda x: int(not pd.isna(x)));
transcript_values['offer_id'] = transcript_values['offer_id'].apply(lambda x: '0' if pd.isna(x) else x);
transcript = pd.concat([transcript, transcript_values, transcript_event], axis=1);
transcript = transcript.drop(['value', 'event', 'reward'], axis=1);
del transcript_value_clean, transcript_values, transcript_event
transcript.head()<jupyter_output><empty_output><jupyter_text>The final **Portfolio**, **Profile**, and **Transcript** datasets are all cleaned, and can be used for further exploration. We'll now create some new datasets from these. These will be- Transactions Only - Store only transactions, not associated with an offer, along with demographic data on the users.- Transcript by Group - Store transactions by grouping them by their offer, and the person it was sent to.<jupyter_code>transcript_profile = pd.merge(transcript, profile, left_on='person', right_on='id')
# keep only records of actual transactions, and keep integer columns
transaction_data_only = transcript_profile[transcript_profile.event_transaction == 1];
transaction_data_only = transaction_data_only.select_dtypes(exclude=['object']);

# separate into male and female dataframes
transaction_data_f = transaction_data_only[transaction_data_only.gender_F == 1]
transaction_data_m = transaction_data_only[transaction_data_only.gender_M == 1]

transaction_data_only.head()
plt.rcParams["figure.figsize"] = (12,4)

fig, (ax1, ax2) = plt.subplots(ncols = 2)

# plotting a Box Plot of amounts by gender
g = sns.boxplot(x="gender_F", y="amount",
            hue="gender_F", palette=["m", "g"],
            data=transaction_data_only, ax=ax1)

# set a log Y scale
g.set_yscale('log')
g.legend().set_title('Gender')
for t, l in zip(g.legend().texts, ['Male', 'Female']): t.set_text(l)

# plot title and axis
plt.title('Amount spent on transactions by Gender')
plt.xlabel('Gender')
plt.ylabel('Amount')
plt.xticks([[]])

# plotting a Bar Plot of number of transactions by gender
h = sns.countplot(transaction_data_only['gender_F'], ax=ax2)

# plot title and axis
plt.title('Number of Transactions by Gender')
plt.xlabel('Gender')
plt.ylabel('Transactions')

plt.xticks([0, 1], ['Male', 'Female'])

# Create a new DataFrame of stat details on required attributes
description_columns = ['amount', 'age', 'income']
description_labels = [cat + '_' + gen for cat in ['Male', 'Female'] for gen in description_columns]
reorder_columns = [gen + '_' + cat for cat in description_columns for gen in ['Male', 'Female']]

# Separate into stats from Female and Male transactions
description = pd.concat([transaction_data_m[['amount', 'age', 'income']].describe(), 
           transaction_data_f[['amount', 'age', 'income']].describe()], axis=1);
description.columns = description_labels;

plt.show()
description[reorder_columns]<jupyter_output><empty_output><jupyter_text>The above three charts answer our questions about **Gender**. We see in the first plot that *Women* spend more on transactions then *Men* do. The interesting thing in the second chart is that it shows that there are a lot more records of purchases by men than those by women.The final chart (table)  gives more detailed statistics on **Amount**, **Age**, and **Income** separated by **Gender**. It shows that *Women* clearly spend more than *Men* do, with higher mean and deviation. Contrary to the early plot on raw data, we see that the **age** of most spenders is similar for both men and women. Finally, the biggest disparity we see is in **Income**. Higher mean and 25/50/70 percentage quantile shows that the average Female spender earns more than the average Male spender.***This answers our first question on the influence of gender of purchases. Women spend more at Starbucks then Men do, with an average difference of about 6 dollars per purchase***. Next, let's look at the **Transcript** dataset in more detail. We saw earlier that the dataset has different types of events associated with a transaction. This includes when an offer is received, viewed, and completed, as well as when it is an actual purchase. In this step, we are going to look for *completion trails*, as in for every offer that is sent to a person, how often to do they actually view and complete the offer by making purchases for it.<jupyter_code>transcript_portfolio = pd.merge(transcript, portfolio, left_on='offer_id', right_on='id', how='left')
transcript_by_group = transcript_portfolio.groupby(['person', 'offer_id'])
%%time

completion_details = [];

'''
Go through each group in the transaction grouping. Because iterating can be slow, 
we will use vectorized operations inside the main loop.
'''
for i, g in transcript_by_group:
    
    record = {};
    
    # Record status based on weather the offer has been viewed
    if g[['event_offer_received', 'event_offer_viewed', 'event_offer_completed']].sum(axis=0).sum() == 3:
        record['status'] = 'viewed_and_completed';
    elif (g[['event_offer_received', 'event_offer_completed']].sum(axis=0).sum() == 2) and 'status' not in record:
        record['status'] = 'not_viewed_and_completed';
    else:
        record['status'] = 'no_offer';
    
    # Get required details
    person_id, offer_id = g['person'].iloc[0], g['offer_id'].iloc[0];
    first_purchase, last_purchase = g['time'].min(), g['time'].max();
    
    # Get all transactions corresponding to the person
    try:
        person_transactions = transactions_only.get_group(person_id);
    except KeyError:
        offer_transactions = [];
    
    # Filter out transactions that are within the time window of the offer
    offer_transactions = person_transactions[(person_transactions.time > first_purchase) \
                                             & (person_transactions.time < last_purchase)];

    # Store these details into the dictionary
    record['num_transactions'] = len(offer_transactions);
    record['person_id'], record['offer_id'] = person_id, offer_id;
    record['first_purchase'], record['last_purchase'] = first_purchase, last_purchase;
    
    #print(person_id, offer_id, num_transactions)
    completion_details.append(record);

print('Completed forming transactions');
completion_details_df = pd.DataFrame(completion_details);<jupyter_output><empty_output><jupyter_text>Having computed the details view, we will plot charts about them. First, we will look at the amount of transactions made on each status. Second, the number of transactions made when they were done corresponding to an offer. And third, a sample of rows on purchases made when an offer was completed without any transactions.<jupyter_code>plt.rcParams["figure.figsize"] = (12,4)

fig, (ax1, ax2) = plt.subplots(ncols = 2)

# Plotting the status of offers
g = sns.countplot(completion_details_df['status'], ax=ax1);
g.set_xticklabels(labels = ['No Offer', 'Viewed and Completed Offer', 'Did not view but completed offer'], rotation=45)

# Plotting number of transactions made when associated with an offer
h = sns.countplot(completion_details_df[completion_details_df.status != 'no_offer']['num_transactions'], ax=ax2)

plt.show()
for val in np.unique(completion_details_df['status']):
    print(val , ' : ' , ((completion_details_df['status'] == val).sum() / len(completion_details_df))*100, '%')

completed_offers_with_zero_purchases = completion_details_df[(completion_details_df.status != 'no_offer') & (completion_details_df.num_transactions == 0)];
completed_offers_with_zero_purchases = pd.merge(completed_offers_with_zero_purchases, portfolio[['id', 'offer_type']], left_on='offer_id', right_on='id')
completed_offers_with_zero_purchases.drop(['offer_id', 'person_id', 'id'], axis=1).sample(5)<jupyter_output><empty_output><jupyter_text>The above charts answer several questions. In the first chart, we see that apart from users who make a purchase not corresponding to an offer, *more people actually complete an offer without receiving it first*. However, around 8,000 people still *utilize offers*.The second chart shows how many transactions are made to complete offers. We can see that thre is a clear skew on 0 transactions. Apart from those, *most offers are completed within 1-2 purchases*. I finally looked at a sample of rows where an offer was completed without any transactions. This showed that the dataset may contain incomplete records.***This answers our 2nd question. 25% of people view and complete offers, while 10% of people complete offers without viewing them first.***  Modeling the dataWith our cleaned data, we can now use Supervised Models to try to predict price. Because **price** is a continous variable, we will need to use a regression model. First, we'll need to separate the data into X and Y.<jupyter_code># Time is relative and will likely not be useful. Amount will be separated into the Y column.

data_x = transaction_data_only.drop(['amount', 'time'], axis=1);
data_y = transaction_data_only['amount'];<jupyter_output><empty_output><jupyter_text>We'll now use a Scaler function to normalize the attributes.<jupyter_code>scaler = StandardScaler();
data_x = scaler.fit_transform(data_x);
data_x.shape, data_y.shape<jupyter_output><empty_output><jupyter_text>And finally, separate the X and Y into a Train/Test split.<jupyter_code>X_train, X_test, y_train, y_test = train_test_split(data_x, data_y, test_size=0.33);
X_train.shape, X_test.shape<jupyter_output><empty_output><jupyter_text>For modeling, let's start by first using a **Support Vector Machine (SVM)** to fit a model to the data. I will use a Polynomial Regression model. The metric I decided to use is a **R2** score, which works well on continious output.<jupyter_code>%%time

sv_model = SVR(kernel='poly', degree=7);
sv_model.fit(X_train, y_train);
print('SVR model trained on 7-degree poly kernel')
%%time

y_preds_svr = sv_model.predict(X_test);
print('SVR Model R2 score: ' , r2_score(y_test, y_preds_svr))<jupyter_output>SVR Model R2 score:  0.0791369487837
CPU times: user 1min 15s, sys: 8.14 ms, total: 1min 15s
Wall time: 1min 16s<jupyter_text>Although the SVM provided good results, we cannot view feature importance because the kernel is non-linear. Feature importance is useful for us because it will help us understand which attributes most contribute to a predicted label. Let's use a different model for this, such as a **Random Forest**.<jupyter_code>%%time

rf_model = RandomForestRegressor(n_estimators=100, max_depth=20, max_features=15, min_samples_split=5);
rf_model.fit(X_train, y_train);
print('RF Model trained with 10 estimators')
%%time

y_preds_rf = rf_model.predict(X_test);
print('RF Model R2 score: ' , r2_score(y_test, y_preds_rf))<jupyter_output>RF Model R2 score:  -0.0823391370849
CPU times: user 1.03 s, sys: 4 ms, total: 1.03 s
Wall time: 1.04 s<jupyter_text>The R2 Score is not as good as that obtained with the SVM, so we should try to tune the model to find parameters that provide the best results. For this, we can use a **Grid Search**. The parameters we will tune are- Number of Estimators, between *10, 50, and 100*- Maximum Depth of a tree, between *5, 10, 30, and 80*- Maximum Number of Features in a tree, between *1, 3, 8, and 15*- Minimum Samples to split on, between *3, 5, 10, 30, 50, and 100*<jupyter_code>params = {'n_estimators' : [10, 50, 100], 'max_depth' : [5, 10, 30, 80], \
          'max_features': [1, 3, 8, 15], 'min_samples_split': [3, 5, 10, 30, 50, 100]}

g_rfm = RandomForestRegressor(random_state=1024);
g_src = GridSearchCV(g_rfm, params, verbose=10, cv=5, scoring='r2');
g_src.fit(X_train, y_train)<jupyter_output>Fitting 5 folds for each of 288 candidates, totalling 1440 fits
[CV] max_depth=5, max_features=1, min_samples_split=3, n_estimators=10 
[CV]  max_depth=5, max_features=1, min_samples_split=3, n_estimators=10, score=0.04195848986474615, total=   0.2s
[CV] max_depth=5, max_features=1, min_samples_split=3, n_estimators=10<jupyter_text>Tuning the model took a very long time, (~5 hours), but now having tuned the model, we can look at the parameters that best fit the dataset.<jupyter_code>g_src.best_params_<jupyter_output><empty_output><jupyter_text>We can use these obtained parameters to train a Tuned model.<jupyter_code>tuned_rf_model = RandomForestRegressor(max_depth=30, max_features=3, min_samples_split=100, n_estimators=100);
tuned_rf_model.fit(X_train, y_train);
y_preds_tuned = tuned_rf_model.predict(X_test);
r2_score(y_test, y_preds_tuned)<jupyter_output><empty_output><jupyter_text>ResultsOur final results on the different trained models are:- 0.0791369487837 with *SVM Regressor*- -0.0823391370849 with *Default Random Forest Regressor*- 0.069935792705576483 with *Tuned Random Forest Regressor*The final **R2** score is better than that obtained with a SVM and with the default Random Forest. There is not a lot of difference between the results of the SVM and Random Forest, and our Grid Search shows that the SVM could be tuned to obtain better results as well.With Random Forest, we can look at Feature Importance of the attributes that were used to train the model.<jupyter_code>pd.DataFrame(list(zip(transaction_data_only.columns[2:], tuned_rf_model.feature_importances_)), columns=['Attribute', 'Feature Importance']).sort_values(by='Feature Importance', ascending=False)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09088564692012968, 'fraction_numerical': 0.013557323902151489, 'mean_word_length': 5.604622871046229, 'pattern_counts': {'":': 0, '<': 91, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '661695', 'license': 'MIT', 'n_tokens_mistral': 8015, 'n_tokens_neox': 7481, 'n_words': 3234, 'path': 'Starbucks_Capstone_notebook.ipynb', 'repo_name': 'ravishchawla/DataAnalysisWithStarbucks', 'size': '27144'}