<jupyter_start><jupyter_text>Anime Rating Case Study  ContextStreamist is a streaming company that streams web series and movies for a worldwide audience. Every content on their portal is rated by the viewers, and the portal also provides other information for the content like the number of people who have watched it, the number of people who want to watch it, the number of episodes, duration of an episode, etc.They are currently focusing on the anime available in their portal, and want to identify the most important factors involved in rating an anime. You as a data scientist at Streamist are tasked with identifying the important factors and building a predictive model to predict the rating on an anime. ObjectiveTo analyze the data and build a linear regression model to predict the ratings of anime. Key Questions1. What are the key factors influencing the rating of an anime?2. Is there a good predictive model for the rating of an anime? What does the performance assessment look like for such a model? Data InformationEach record in the database provides a description of an anime. A detailed data dictionary can be found below.**Data Dictionary**- title - the title of anime- description - the synopsis of the plot- mediaType - format of publication- eps - number of episodes (movies are considered 1 episode)- duration - duration of an episode in minutes- ongoing - whether it is ongoing- sznOfRelease - the season of release (Winter, Spring, Fall)- years_running - number of years the anime ran/is running- studio_primary - primary studio of production- studios_colab - whether there was a collaboration between studios to produce the anime- contentWarn - whether anime has a content warning- watched - number of users that completed it- watching - number of users that are watching it- wantWatch - number of users that want to watch it- dropped - number of users that dropped it before completion- rating - average user rating- votes - number of votes that contribute to rating- tag_Based_on_a_Manga - whether the anime is based on a manga- tag_Comedy - whether the anime is of Comedy genre- tag_Action - whether the anime is of Action genre- tag_Fantasy - whether the anime is of Fantasy genre- tag_Sci_Fi - whether the anime is of Sci-Fi genre- tag_Shounen - whether the anime has a tag Shounen- tag_Original_Work - whether the anime is an original work- tag_Non_Human_Protagonists - whether the anime has any non-human protagonists- tag_Drama - whether the anime is of Drama genre- tag_Adventure - whether the anime is of Adventure genre- tag_Family_Friendly - whether the anime is family-friendly- tag_Short_Episodes - whether the anime has short episodes- tag_School_Life - whether the anime is regarding school life- tag_Romance - whether the anime is of Romance genre- tag_Shorts - whether the anime has a tag Shorts- tag_Slice_of_Life - whether the anime has a tag Slice of Life- tag_Seinen - whether the anime has a tag Seinen- tag_Supernatural - whether the anime has a tag Supernatural- tag_Magic - whether the anime has a tag Magic- tag_Animal_Protagonists - whether the anime has animal protagonists- tag_Ecchi - whether the anime has a tag Ecchi- tag_Mecha - whether the anime has a tag Mecha- tag_Based_on_a_Light_Novel - whether the anime is based on a light novel- tag_CG_Animation - whether the anime has a tag CG Animation- tag_Superpowers - whether the anime has a tag Superpowers- tag_Others - whether the anime has other tags- tag_is_missing - whether tag is missing or not  Let's start coding!Install the nb_black package`conda install -c conda-forge nb_black`  Import necessary libraries<jupyter_code># this will help in making the Python code more structured automatically (good coding practice)
%load_ext nb_black

# Libraries to help with reading and manipulating data
import numpy as np
import pandas as pd

# Libraries to help with data visualization
import matplotlib.pyplot as plt
import seaborn as sns

sns.set()

# Removes the limit for the number of displayed columns
pd.set_option("display.max_columns", None)
# Sets the limit for the number of displayed rows
pd.set_option("display.max_rows", 200)

# to split the data into train and test
from sklearn.model_selection import train_test_split

# to build linear regression_model
from sklearn.linear_model import LinearRegression

# to check model performance
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
# loading the dataset
df = pd.read_csv("anime_data.csv")
# checking shape of the data
print(f"There are {df.shape[0]} rows and {df.shape[1]} columns.")
# to view first 5 rows of the dataset
df.head()
# to view last 5 rows of the dataset
df.tail()
# let's create a copy of the data to avoid any changes to original data
data = df.copy()
# checking for duplicate values in the data
data.duplicated().sum()<jupyter_output><empty_output><jupyter_text>- There are no duplicate values in the data.<jupyter_code># checking the names of the columns in the data
print(data.columns)
# checking column datatypes and number of non-null values
data.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 12101 entries, 0 to 12100
Data columns (total 44 columns):
 #   Column                      Non-Null Count  Dtype  
---  ------                      --------------  -----  
 0   title                       12101 non-null  object 
 1   description                 7633 non-null   object 
 2   mediaType                   12101 non-null  object 
 3   eps                         12101 non-null  int64  
 4   duration                    7465 non-null   float64
 5   ongoing                     12101 non-null  bool   
 6   sznOfRelease                12101 non-null  object 
 7   years_running               12101 non-null  int64  
 8   studio_primary              12101 non-null  object 
 9   studios_colab               12101 non-null  int64  
 10  contentWarn                 12101 non-null  int64  
 11  watched                     12101 non-null  float64
 12  watching                    12101 non-null  int64  
 13  wantWatch                   121[...]<jupyter_text>* Dependent variable is the rating of an anime, which if of *float* type.* *title*, *description*, *mediaType*, *sznOfRelease*, *studio_primary* are of *object* type.* *ongoing* column is of *bool* type.* All other columns are numeric in nature.* There are missing values in the *description* and *duration* columns. **Let's check for missing values in the data.**<jupyter_code># checking for missing values in the data
data.isnull().sum()<jupyter_output><empty_output><jupyter_text>* *duration* column has 4636 missing values, and *description* column has 4468 missing values.* No other column has missing values.<jupyter_code># Let's look at the statistical summary of the data
data.describe().T<jupyter_output><empty_output><jupyter_text>* We can see that the anime ratings vary between 0.844 and 4.702, which suggests that the anime were rated on a scale of 0-5.* 50% of the anime in the data have a runtime less than or equal to 8 minutes.* Some anime even have a runtime of 1 minute.    - This seems strange at first, but a Google search can reveal that there are indeed such anime.* At least 75% of the anime have run for less than a year.    - This may be because the listed anime has few episodes only.* At least 75% of the anime have no content warnings.* The number of views for the anime in the data has a very wide range (0 to more than 160,000). **Let's look at the non-numeric columns.**<jupyter_code># filtering non-numeric columns
cat_columns = data.select_dtypes(exclude=np.number).columns
cat_columns
# we will skip the title and description columns as they will have a lot of unique values
cat_col = ["mediaType", "ongoing", "sznOfRelease", "studio_primary"]

# printing the number of occurrences of each unique value in each categorical column
for column in cat_col:
    print(data[column].value_counts())
    print("-" * 50)<jupyter_output>TV             3993
Movie          1928
OVA            1770
Music Video    1290
Web            1170
DVD Special     803
Other           580
TV Special      504
is_missing       63
Name: mediaType, dtype: int64
--------------------------------------------------
False    11986
True       115
Name: ongoing, dtype: int64
--------------------------------------------------
is_missing    8554
Spring        1135
Fall          1011
Winter         717
Summer         684
Name: sznOfRelease, dtype: int64
--------------------------------------------------
Others                  4340
is_missing              3208
Toei Animation           636
Sunrise                  430
J.C. Staff               341
MADHOUSE                 337
TMS Entertainment        317
Production I.G           271
Studio Deen              260
Studio Pierrot           221
OLM                      210
A-1 Pictures             194
AIC                      167
Shin-Ei Animation        164
Nippon Animation         145
Tatsunoko Produc[...]<jupyter_text>* Most of the anime in the data are either TV series or movies.* Most of the anime in the data are not ongoing.* The season of release is missing for most of the anime in the data.* *Toei Animation* and *Sunrise* are the top two studios (excluding other studios and missing studios). **We will drop the *title* and *description* columns before moving forward as they have a lot of text in them.**<jupyter_code>data.drop(["title", "description"], axis=1, inplace=True)<jupyter_output><empty_output><jupyter_text>We will drop the missing values in the dataset.<jupyter_code>data.dropna(inplace=True)
data.shape<jupyter_output><empty_output><jupyter_text>We will discuss better ways of dealing with missing values in the next session.  Let's visualize the data  Univariate Analysis<jupyter_code># function to plot a boxplot and a histogram along the same scale.


def histogram_boxplot(data, feature, figsize=(12, 7), kde=False, bins=None):
    """
    Boxplot and histogram combined

    data: dataframe
    feature: dataframe column
    figsize: size of figure (default (12,7))
    kde: whether to the show density curve (default False)
    bins: number of bins for histogram (default None)
    """
    f2, (ax_box2, ax_hist2) = plt.subplots(
        nrows=2,  # Number of rows of the subplot grid= 2
        sharex=True,  # x-axis will be shared among all subplots
        gridspec_kw={"height_ratios": (0.25, 0.75)},
        figsize=figsize,
    )  # creating the 2 subplots
    sns.boxplot(
        data=data, x=feature, ax=ax_box2, showmeans=True, color="violet"
    )  # boxplot will be created and a star will indicate the mean value of the column
    sns.histplot(
        data=data, x=feature, kde=kde, ax=ax_hist2, bins=bins, palette="winter"
    ) if bins else sns.histplot(
        data=data, x=feature, kde=kde, ax=ax_hist2
    )  # For histogram
    ax_hist2.axvline(
        data[feature].mean(), color="green", linestyle="--"
    )  # Add mean to the histogram
    ax_hist2.axvline(
        data[feature].median(), color="black", linestyle="-"
    )  # Add median to the histogram<jupyter_output><empty_output><jupyter_text>*rating* (the target variable)<jupyter_code>histogram_boxplot(data, "rating")<jupyter_output><empty_output><jupyter_text>* The anime ratings are close to normally distributed.  *eps*<jupyter_code>histogram_boxplot(data, "eps", bins=100)<jupyter_output><empty_output><jupyter_text>* The distribution is heavily right-skewed, as there are many anime movies in the data, and they are considered to be of only one episode (as per data description).  *duration*<jupyter_code>histogram_boxplot(data, "duration")<jupyter_output><empty_output><jupyter_text>* The distribution is right-skewed with a median runtime of less than 10 minutes.  *watched*<jupyter_code>histogram_boxplot(data, "watched", bins=50)<jupyter_output><empty_output><jupyter_text>* The distribution is heavily right-skewed, and most of the anime having less than 500 viewers.  *years_running*<jupyter_code>histogram_boxplot(data, "years_running")<jupyter_output><empty_output><jupyter_text>* The distribution is heavily right-skewed, and most of the anime have run for less than 1 year.<jupyter_code># function to create labeled barplots


def labeled_barplot(data, feature, perc=False, n=None):
    """
    Barplot with percentage at the top

    data: dataframe
    feature: dataframe column
    perc: whether to display percentages instead of count (default is False)
    n: displays the top n category levels (default is None, i.e., display all levels)
    """

    total = len(data[feature])  # length of the column
    count = data[feature].nunique()
    if n is None:
        plt.figure(figsize=(count + 1, 5))
    else:
        plt.figure(figsize=(n + 1, 5))

    plt.xticks(rotation=90, fontsize=15)
    ax = sns.countplot(
        data=data,
        x=feature,
        palette="Paired",
        order=data[feature].value_counts().index[:n].sort_values(),
    )

    for p in ax.patches:
        if perc == True:
            label = "{:.1f}%".format(
                100 * p.get_height() / total
            )  # percentage of each class of the category
        else:
            label = p.get_height()  # count of each level of the category

        x = p.get_x() + p.get_width() / 2  # width of the plot
        y = p.get_height()  # height of the plot

        ax.annotate(
            label,
            (x, y),
            ha="center",
            va="center",
            size=12,
            xytext=(0, 5),
            textcoords="offset points",
        )  # annotate the percentage

    plt.show()  # show the plot<jupyter_output><empty_output><jupyter_text>*mediaType*<jupyter_code>labeled_barplot(data, "mediaType", perc=True)<jupyter_output><empty_output><jupyter_text>* Distribution of the media types has changed a lot after dropping the rows with missing values.* Most of the anime now are movies or music videos.  *ongoing*<jupyter_code>labeled_barplot(data, "ongoing", perc=True)<jupyter_output><empty_output><jupyter_text>* Very few anime in the data are ongoing.  *sznOfRelease*<jupyter_code>labeled_barplot(data, "sznOfRelease", perc=True)<jupyter_output><empty_output><jupyter_text>* The season of release of anime is spread out across all seasons when the value is available.  *studio_primary*<jupyter_code>labeled_barplot(data, "studio_primary", perc=True)<jupyter_output><empty_output><jupyter_text>* *Toei Animation* is the most common studio among the available studio names.<jupyter_code># creating a list of tag columns
tag_cols = [item for item in data.columns if "tag" in item]

# printing the number of occurrences of each unique value in each categorical column
for column in tag_cols:
    print(data[column].value_counts())
    print("-" * 50)<jupyter_output>0    5718
1    1747
Name: tag_Based_on_a_Manga, dtype: int64
--------------------------------------------------
0    5545
1    1920
Name: tag_Comedy, dtype: int64
--------------------------------------------------
0    6227
1    1238
Name: tag_Action, dtype: int64
--------------------------------------------------
0    6342
1    1123
Name: tag_Fantasy, dtype: int64
--------------------------------------------------
0    6511
1     954
Name: tag_Sci_Fi, dtype: int64
--------------------------------------------------
0    6625
1     840
Name: tag_Shounen, dtype: int64
--------------------------------------------------
0    6548
1     917
Name: tag_Original_Work, dtype: int64
--------------------------------------------------
0    6553
1     912
Name: tag_Non_Human_Protagonists, dtype: int64
--------------------------------------------------
0    6896
1     569
Name: tag_Drama, dtype: int64
--------------------------------------------------
0    6913
1     552
Name: tag_Adventure, dtype: [...]<jupyter_text>* There are 1747 anime that are based on manga.* There are 1920 anime of the Comedy genre.* There are 1079 anime of the Romance genre.  Bivariate analysis **We will not consider the *tag* columns for correlation check as they have only 0 or 1 values.**<jupyter_code># creating a list of non-tag columns
corr_cols = [item for item in data.columns if "tag" not in item]
print(corr_cols)
plt.figure(figsize=(15, 7))
sns.heatmap(
    df[corr_cols].corr(), annot=True, vmin=-1, vmax=1, fmt=".2f", cmap="Spectral"
)
plt.show()<jupyter_output><empty_output><jupyter_text>* *watched* and *wantWatch* columns are highly correlated.* *watched* and *votes* columns are very highly correlated.* *wantWatch* and *votes* columns are highly correlated.  Let's check the variation in *rating* with some of the categorical columns in our data  *mediaType* vs *rating*<jupyter_code>plt.figure(figsize=(10, 5))
sns.boxplot(x="mediaType", y="rating", data=data)
plt.show()<jupyter_output><empty_output><jupyter_text>* Anime available as TV series, web series, or music videos have a lower rating in general.  *sznOfRelease* vs *rating*<jupyter_code>plt.figure(figsize=(10, 5))
sns.boxplot(x="sznOfRelease", y="rating", data=data)
plt.show()<jupyter_output><empty_output><jupyter_text>* Anime ratings are almost uniformly spread across all the seasons of release.  *studio_primary* vs *rating*<jupyter_code>plt.figure(figsize=(15, 5))
sns.boxplot(x="studio_primary", y="rating", data=data)
plt.xticks(rotation=90)
plt.show()<jupyter_output><empty_output><jupyter_text>* In general, the ratings are low for anime produced by *DLE* studios.* Ratings are also low, in general, for anime produced by studios other than the ones in the data. **We will drop the *votes* column from the data as *watched* and *votes* columns are very highly correlated.**<jupyter_code>data.drop("votes", axis=1, inplace=True)
data.shape<jupyter_output><empty_output><jupyter_text>Model Building  Define independent and dependent variables<jupyter_code>X = data.drop(["rating"], axis=1)
y = data["rating"]<jupyter_output><empty_output><jupyter_text>Creating dummy variables<jupyter_code>X = pd.get_dummies(
    X,
    columns=X.select_dtypes(include=["object", "category"]).columns.tolist(),
    drop_first=True,
)
X.head()
X.shape<jupyter_output><empty_output><jupyter_text>Split the data into train and test<jupyter_code># splitting the data in 70:30 ratio for train to test data

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)
print("Number of rows in train data =", x_train.shape[0])
print("Number of rows in test data =", x_test.shape[0])<jupyter_output>Number of rows in train data = 5225
Number of rows in test data = 2240<jupyter_text>Fitting a linear model<jupyter_code>lin_reg_model = LinearRegression()
lin_reg_model.fit(x_train, y_train)<jupyter_output><empty_output><jupyter_text>**Let us check the coefficients and intercept of the model, which are the parameters we learned.**<jupyter_code>coef_df = pd.DataFrame(
    np.append(lin_reg_model.coef_, lin_reg_model.intercept_),
    index=x_train.columns.tolist() + ["Intercept"],
    columns=["Coefficients"],
)
coef_df<jupyter_output><empty_output><jupyter_text>Coefficient Interpretations* Coefficients of *eps*, *duration*, *years_running*, *ongoing*, some of the *tag* columns, all the *sznOfRelease* columns, some of the *studio* columns, etc. are positive.    - Increase in these will lead to an increase in the rating of an anime.* Coefficients of *contentWarn*, *watched*, *dropped*, some of the *tag* columns, all the *mediaType* columns, and some of the *studio* columns are negative.    - Increase in these will lead to a decrease in the rating of an anime.  Model performance check * We will be using metric functions defined in sklearn for RMSE, MAE, and $R^2$.* We will define functions to calculate adjusted $R^2$ and MAPE.    - The mean absolute percentage error (MAPE) measures the accuracy of predictions as a percentage, and can be calculated as the average absolute percent error for each predicted value minus actual values divided by actual values. It works best if there are no extreme values in the data and none of the actual values are 0.    * We will create a function that will print out all the above metrics in one go.<jupyter_code># function to compute adjusted R-squared
def adj_r2_score(predictors, targets, predictions):
    r2 = r2_score(targets, predictions)
    n = predictors.shape[0]
    k = predictors.shape[1]
    return 1 - ((1 - r2) * (n - 1) / (n - k - 1))


# function to compute MAPE
def mape_score(targets, predictions):
    return np.mean(np.abs(targets - predictions) / targets) * 100


# function to compute different metrics to check performance of a regression model
def model_performance_regression(model, predictors, target):
    """
    Function to compute different metrics to check regression model performance

    model: regressor
    predictors: independent variables
    target: dependent variable
    """

    # predicting using the independent variables
    pred = model.predict(predictors)

    r2 = r2_score(target, pred)  # to compute R-squared
    adjr2 = adj_r2_score(predictors, target, pred)  # to compute adjusted R-squared
    rmse = np.sqrt(mean_squared_error(target, pred))  # to compute RMSE
    mae = mean_absolute_error(target, pred)  # to compute MAE
    mape = mape_score(target, pred)  # to compute MAPE

    # creating a dataframe of metrics
    df_perf = pd.DataFrame(
        {
            "RMSE": rmse,
            "MAE": mae,
            "R-squared": r2,
            "Adj. R-squared": adjr2,
            "MAPE": mape,
        },
        index=[0],
    )

    return df_perf
# Checking model performance on train set
print("Training Performance\n")
lin_reg_model_train_perf = model_performance_regression(lin_reg_model, x_train, y_train)
lin_reg_model_train_perf
# Checking model performance on test set
print("Test Performance\n")
lin_reg_model_test_perf = model_performance_regression(lin_reg_model, x_test, y_test)
lin_reg_model_test_perf<jupyter_output>Test Performance<jupyter_text>**Observations**- The train and test $R^2$ are 0.52 and 0.51, indicating that the model explains 52% and 51% of the total variation in the train and test sets respectively. Also, both scores are comparable.- RMSE values on the train and test sets are also comparable.- This shows that the model is not overfitting.- MAE indicates that our current model is able to predict anime ratings within a mean error of 0.47- MAPE of 19.46 on the test data means that we are able to predict within 19.5% of the anime rating.- However, the overall performance is not so great.  Forward Feature Selection using *SequentialFeatureSelector*We will see how to select a subset of important features with forward feature selection using *SequentialFeatureSelector*.**Why should we do feature selection?**- Reduces dimensionality- Discards deceptive features (Deceptive features appear to aid learning on the training set, but impair generalization)- Speeds training/testing**How does forward feature selection work?*** It starts with an empty model and adds variables one by one.* In each forward step, you add the one variable that gives the highest improvement to your model.We'll use forward feature selection on all the variables<jupyter_code># please uncomment and run the next line if mlxtend library is not previously installed
#!pip install mlxtend
from mlxtend.feature_selection import SequentialFeatureSelector as SFS

reg = LinearRegression()

# Build step forward feature selection
sfs = SFS(
    reg,
    k_features=x_train.shape[1],
    forward=True,  # k_features denotes the number of features to select
    floating=False,
    scoring="r2",
    n_jobs=-1,  # n_jobs=-1 means all processor cores will be used
    verbose=2,
    cv=5,
)

# Perform SFFS
sfs = sfs.fit(x_train, y_train)
# to plot the performance with addition of each feature
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs

fig1 = plot_sfs(sfs.get_metric_dict(), kind="std_err", figsize=(15, 5))
plt.title("Sequential Forward Selection (w. StdErr)")
plt.xticks(rotation=90)
plt.show()<jupyter_output><empty_output><jupyter_text>* We can see that performance increases till the 30th feature and then slowly becomes constant, and then drops sharply after the 70th feature is added.* The decision to choose the k_features now depends on the adjusted $R^2$ vs the complexity of the model.    - With 30 features, we are getting an adjusted $R^2$ of 0.504 and with 48 features, we are getting an adjusted $R^2$ of 0.507.* The increase in adjusted $R^2$ is not very significant as we are getting the same values with a less complex model.* So we'll use 30 features only to build our model, but you can experiment by taking a different number.- Number of features chosen will also depend on the business context and use case of the model.<jupyter_code>reg = LinearRegression()

# Build step forward feature selection
sfs = SFS(
    reg,
    k_features=30,
    forward=True,
    floating=False,
    scoring="r2",
    n_jobs=-1,
    verbose=2,
    cv=5,
)

# Perform SFFS
sfs = sfs.fit(x_train, y_train)
# let us select the features which are important
feat_cols = list(sfs.k_feature_idx_)
print(feat_cols)
# let us look at the names of the important features
x_train.columns[feat_cols]<jupyter_output><empty_output><jupyter_text>**Now we will fit an sklearn model using these features only.**<jupyter_code>x_train_final = x_train[x_train.columns[feat_cols]]
# Creating new x_test with the same 20 variables that we selected for x_train
x_test_final = x_test[x_train_final.columns]
# Fitting linear model
lin_reg_model2 = LinearRegression()
lin_reg_model2.fit(x_train_final, y_train)

# let us check the coefficients and intercept of the model

coef_df = pd.DataFrame(
    np.append(lin_reg_model2.coef_.flatten(), lin_reg_model2.intercept_),
    index=x_train_final.columns.tolist() + ["Intercept"],
    columns=["Coefficients"],
)
print(coef_df)
# model performance on train set
print("Training Performance\n")
lin_reg_model2_train_perf = model_performance_regression(
    lin_reg_model2, x_train_final, y_train
)
lin_reg_model2_train_perf
# model performance on test set
print("Test Performance\n")
lin_reg_model2_test_perf = model_performance_regression(
    lin_reg_model2, x_test_final, y_test
)
lin_reg_model2_test_perf<jupyter_output>Test Performance<jupyter_text>- The performance looks slightly worse than the previous model.- Let's compare the two models we built.<jupyter_code># training performance comparison

models_train_comp_df = pd.concat(
    [lin_reg_model_train_perf.T, lin_reg_model2_train_perf.T],
    axis=1,
)

models_train_comp_df.columns = [
    "Linear Regression sklearn",
    "Linear Regression sklearn (SFS features)",
]

print("Training performance comparison:")
models_train_comp_df
# test performance comparison

models_test_comp_df = pd.concat(
    [lin_reg_model_test_perf.T, lin_reg_model2_test_perf.T],
    axis=1,
)

models_test_comp_df.columns = [
    "Linear Regression sklearn",
    "Linear Regression sklearn (SFS features)",
]

print("Test performance comparison:")
models_test_comp_df<jupyter_output>Test performance comparison:
{'fraction_non_alphanumeric': 0.09966054165744226, 'fraction_numerical': 0.021548225223230758, 'mean_word_length': 3.5597240915208612, 'pattern_counts': {'":': 6, '<': 131, '<?xml version=': 0, '>': 131, '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': '34', 'dir': 'jupyter-structured-clean-dedup', 'id': '119023', 'license': 'Apache-2.0', 'n_tokens_mistral': 8330, 'n_tokens_neox': 7746, 'n_words': 3037, 'path': '02 Supervised Learning - Regression/Week 5 - Intro to Supervised Learning - Linear Regression/MLS AnimeRatingPrediction (Part 1).ipynb', 'repo_name': 'senthilprabhut/DataScience', 'size': '27102'}