{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Clustering with News Publications\n",
"Author: Matthew Huh\n",
" \n",
"## Overview\n",
"\n",
"For the most part, people are free to choose what news outlets they read and follow. In the United States, there is a near-endless list of sites that people can choose from in order to get their daily news and over time, they develop preferences for sites that they are more attached to, and do their best to avoid. Now these affinities are developed through a combination of means ranging from affiliations, vocabulary, prose, and so forth.\n",
"\n",
"What I would like to examine in this project is if it is possible to differentiate from several different publications with their respective perks / quirks. \n",
"\n",
"## About the Data\n",
"\n",
"This dataset was obtained from Kaggle, and contains a collection of 142,570 articles from 15 different publications.\n",
"\n",
"The publications within this dataset are\n",
"1. CNN\n",
"2. Breitbart\n",
"3. Vox\n",
"4. Washington Post\n",
"5. New York Post\n",
"6. National Review\n",
"7. NPR\n",
"8. Guardian\n",
"9. Talking Points Memo\n",
"10. Atlantic\n",
"11. Reuters\n",
"12. Fox News\n",
"13. Business Insider\n",
"14. Buzzfeed News\n",
"15. New York Times\n",
"\n",
"## Research Question\n",
"\n",
"As this is an unsupervised learning project first and foremost, the project will have 3 goals.\n",
"\n",
"1. The first goal is to prepare the articles in the dataset for modelling using various Natural Language Processing (NLP) methods to re-represent the data in numbers rather than words\n",
"2. Cluster the data to determine if we can identify the articles and associate them as different groups.\n",
"3. Determine if we can predict the structure of the article based on the publisher.\n",
"\n",
"## Packages"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# Basic imports\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"import scipy\n",
"import sklearn\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"%matplotlib inline\n",
"\n",
"# Machine Learning packages\n",
"from sklearn.feature_selection import SelectKBest, f_classif\n",
"from sklearn.feature_selection import chi2\n",
"from sklearn.preprocessing import normalize\n",
"from sklearn import ensemble\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.model_selection import cross_val_score\n",
"from sklearn.model_selection import GridSearchCV\n",
"\n",
"# Clustering packages\n",
"import sklearn.cluster as cluster\n",
"from sklearn.cluster import KMeans\n",
"from sklearn.cluster import MeanShift, estimate_bandwidth\n",
"from sklearn.cluster import SpectralClustering\n",
"from sklearn.cluster import AffinityPropagation\n",
"from scipy.spatial.distance import cdist\n",
"\n",
"# Natural Language processing\n",
"import re\n",
"import spacy\n",
"import nltk\n",
"from nltk.corpus import stopwords\n",
"from nltk.stem import WordNetLemmatizer\n",
"from collections import Counter\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.datasets import fetch_rcv1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data Preview\n",
"\n",
"The first matter of business is to import the articles from a local directory and merge them."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Unnamed: 0
\n",
"
id
\n",
"
title
\n",
"
publication
\n",
"
author
\n",
"
date
\n",
"
year
\n",
"
month
\n",
"
url
\n",
"
content
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
0
\n",
"
17283
\n",
"
House Republicans Fret About Winning Their Hea...
\n",
"
New York Times
\n",
"
Carl Hulse
\n",
"
2016-12-31
\n",
"
2016.0
\n",
"
12.0
\n",
"
NaN
\n",
"
WASHINGTON — Congressional Republicans have...
\n",
"
\n",
"
\n",
"
1
\n",
"
1
\n",
"
17284
\n",
"
Rift Between Officers and Residents as Killing...
\n",
"
New York Times
\n",
"
Benjamin Mueller and Al Baker
\n",
"
2017-06-19
\n",
"
2017.0
\n",
"
6.0
\n",
"
NaN
\n",
"
After the bullet shells get counted, the blood...
\n",
"
\n",
"
\n",
"
2
\n",
"
2
\n",
"
17285
\n",
"
Tyrus Wong, ‘Bambi’ Artist Thwarted by Racial ...
\n",
"
New York Times
\n",
"
Margalit Fox
\n",
"
2017-01-06
\n",
"
2017.0
\n",
"
1.0
\n",
"
NaN
\n",
"
When Walt Disney’s “Bambi” opened in 1942, cri...
\n",
"
\n",
"
\n",
"
3
\n",
"
3
\n",
"
17286
\n",
"
Among Deaths in 2016, a Heavy Toll in Pop Musi...
\n",
"
New York Times
\n",
"
William McDonald
\n",
"
2017-04-10
\n",
"
2017.0
\n",
"
4.0
\n",
"
NaN
\n",
"
Death may be the great equalizer, but it isn’t...
\n",
"
\n",
"
\n",
"
4
\n",
"
4
\n",
"
17287
\n",
"
Kim Jong-un Says North Korea Is Preparing to T...
\n",
"
New York Times
\n",
"
Choe Sang-Hun
\n",
"
2017-01-02
\n",
"
2017.0
\n",
"
1.0
\n",
"
NaN
\n",
"
SEOUL, South Korea — North Korea’s leader, ...
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Unnamed: 0 id title \\\n",
"0 0 17283 House Republicans Fret About Winning Their Hea... \n",
"1 1 17284 Rift Between Officers and Residents as Killing... \n",
"2 2 17285 Tyrus Wong, ‘Bambi’ Artist Thwarted by Racial ... \n",
"3 3 17286 Among Deaths in 2016, a Heavy Toll in Pop Musi... \n",
"4 4 17287 Kim Jong-un Says North Korea Is Preparing to T... \n",
"\n",
" publication author date year month \\\n",
"0 New York Times Carl Hulse 2016-12-31 2016.0 12.0 \n",
"1 New York Times Benjamin Mueller and Al Baker 2017-06-19 2017.0 6.0 \n",
"2 New York Times Margalit Fox 2017-01-06 2017.0 1.0 \n",
"3 New York Times William McDonald 2017-04-10 2017.0 4.0 \n",
"4 New York Times Choe Sang-Hun 2017-01-02 2017.0 1.0 \n",
"\n",
" url content \n",
"0 NaN WASHINGTON — Congressional Republicans have... \n",
"1 NaN After the bullet shells get counted, the blood... \n",
"2 NaN When Walt Disney’s “Bambi” opened in 1942, cri... \n",
"3 NaN Death may be the great equalizer, but it isn’t... \n",
"4 NaN SEOUL, South Korea — North Korea’s leader, ... "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Create list of files from directory\n",
"filelist = os.listdir('articles')\n",
"\n",
"# Import the files\n",
"df_list = [pd.read_csv(file) for file in filelist]\n",
"\n",
"#concatenate them together\n",
"articles = pd.concat(df_list)\n",
"\n",
"# Preview the data\n",
"articles.head()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(142570, 10)"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Print the size of the dataset\n",
"articles.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So we have 142,570 articles in the dataset but unfortunately, NLP is quite memory intensive, so we will have to sample the dataset unless you happen to have over 120 GB of memory on your local device. Using a 10% sample still leaves us with 140,000 articles and will be used for the duration of this project."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Sample the dataset for optimal performance\n",
"articles = articles.sample(frac=0.1)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array(['Washington Post', 'CNN', 'New York Post', 'Buzzfeed News', 'NPR',\n",
" 'Guardian', 'Breitbart', 'Atlantic', 'Business Insider',\n",
" 'National Review', 'New York Times', 'Talking Points Memo',\n",
" 'Reuters', 'Vox', 'Fox News'], dtype=object)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Print out unique publisher names\n",
"articles.publication.unique()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"title 14251\n",
"publication 15\n",
"author 3957\n",
"date 1056\n",
"url 8605\n",
"content 14246\n",
"dtype: int64"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Describe unique occurences for each categorical variable\n",
"articles.select_dtypes(include=['object']).nunique()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are also other ways to trim down the dataset before processing. We aren't particularly interested in examining the dates for this research question, but it may be of interest in another. Let's check to see how many articles each author wrote; it may not be very useful to examine authors that are only responsible for a single article, as different authors from the same publisher may choose compose their works differently."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Drop variables that have no impact on the outcome\n",
"articles = articles[['title', 'publication', 'author', 'content']]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"author\n",
"Breitbart News 144\n",
"Associated Press 130\n",
"Pam Key 120\n",
"Charlie Spiering 94\n",
"Daniel Nussbaum 86\n",
"Jerome Hudson 85\n",
"AWR Hawkins 84\n",
"John Hayward 74\n",
"Warner Todd Huston 66\n",
"Joel B. Pollak 63\n",
"Camila Domonoske 63\n",
"Breitbart London 60\n",
"Post Editorial Board 56\n",
"Ian Hanchett 55\n",
"Trent Baker 52\n",
"Reuters 52\n",
"Alex Swoyer 49\n",
"David French 46\n",
"NPR Staff 43\n",
"Bob Price 42\n",
"Charlie Nash 41\n",
"Jeff Poor 41\n",
"David A. Graham 39\n",
"German Lopez 38\n",
"Breitbart Jerusalem 38\n",
"Ben Kew 38\n",
"Keith J. Kelly 36\n",
"Bill Chappell 35\n",
"Esme Cribb 35\n",
"Katherine Rodriguez 34\n",
" ... \n",
"Larry Celona, Tina Moore and Laura Italiano 1\n",
"Larry Celona, Tina Moore and Kenneth Garger 1\n",
"Larry Celona, Tina Moore and Jazmin Rosa 1\n",
"Larry Celona, Shawn Cohen and Natalie Musumeci 1\n",
"Larry Celona, Jennifer Bain, Rebecca Rosenberg and Shawn Cohen 1\n",
"Larry Celona, Jamie Schram and Emily Saul 1\n",
"Lauren Russell 1\n",
"Lauren Sommer 1\n",
"Lauren Windle, The Sun 1\n",
"Laurence Blair 1\n",
"Lesley Wroughton and Yeganeh Torbati 1\n",
"Lesley McClurg 1\n",
"Lenore Skenazy 1\n",
"Lela Moore and Sona Patel 1\n",
"Lela Moore and Lindsey Underwood 1\n",
"Leika Kihara and Tetsushi Kajimoto 1\n",
"Leigh Alexander 1\n",
"Lee Liberman Otis 1\n",
"Lee Glendinning 1\n",
"Leanna Garfield 1\n",
"Lawrence Torcello 1\n",
"Lawrence Summers 1\n",
"Lawrence Ostlere 1\n",
"Lawrence K. Altman, M.d. 1\n",
"Lawrence Hurley and Valerie Volcovici 1\n",
"Lawrence Hurley and Richard Cowan 1\n",
"Lawrence Hurley and Andrew Chung 1\n",
"Laurie Goodstein 1\n",
"Laurie Goering 1\n",
" Faith Karimi 1\n",
"Length: 3957, dtype: int64"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# View most frequently occurring authors\n",
"articles.groupby(['author']).size().sort_values(ascending=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Well, that partly explains how there are so many authors in this dataset. It seems as though there are over 15,000 authors, and many of them have only published one article, or have co-written multiple articles with other authors. This complicates the problem, so in order to best represent each author's writing style, let's see what happens if we simply remove all authors that only published one article as is."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.plotly.v1+json": {
"data": [
{
"labels": [
"Washington Post",
"CNN",
"New York Post",
"Buzzfeed News",
"NPR",
"Guardian",
"Breitbart",
"Atlantic",
"Business Insider",
"National Review",
"New York Times",
"Talking Points Memo",
"Reuters",
"Vox",
"Fox News"
],
"type": "pie",
"values": [
2312,
1809,
1168,
1167,
1119,
1104,
838,
816,
731,
672,
599,
502,
485,
482,
453
]
}
],
"layout": {
"autosize": false,
"height": 600,
"title": "Articles by Publication",
"width": 800
}
},
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Plotly packages\n",
"import plotly as py\n",
"import plotly.graph_objs as go\n",
"from plotly import tools\n",
"import cufflinks as cf\n",
"import ipywidgets as widgets\n",
"from scipy import special\n",
"py.offline.init_notebook_mode(connected=True)\n",
"\n",
"# Pass in values for our pie chart\n",
"trace = go.Pie(labels=articles['publication'].unique(), values = articles['publication'].value_counts())\n",
"\n",
"# Create the layout\n",
"layout = go.Layout(\n",
" title = 'Articles by Publication',\n",
" height = 600,\n",
" width = 800,\n",
" autosize = False\n",
")\n",
"\n",
"# Construct the chart\n",
"fig = go.Figure(data = [trace], layout = layout)\n",
"py.offline.iplot(fig, filename ='cufflinks/simple')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Feature Selection"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# Drop author from the dataframe if they wrote less than 5 articles\n",
"vc = articles['author'].value_counts()\n",
"u = [i not in set(vc[vc<=4].index) for i in articles['author']]\n",
"articles = articles[u]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"title 9324\n",
"publication 15\n",
"author 608\n",
"content 9318\n",
"dtype: int64"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Reprint how many unique authors there are\n",
"articles.select_dtypes(include=['object']).nunique()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(9326, 4)"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# View number of articles after feature selection\n",
"articles.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So after removing authors that composed fewer than 5 articles, we are left with 9k articles, or 67% of the data, and roughly 600/3900 of the authors. Now, we can create a better representation of each author since each author has at least 5 articles to evaluate from.\n",
"\n",
"## Text Cleaning\n",
"\n",
"Now that we've chosen which articles to use, it's time to clean them up and prepare them for feature engineering. What this section covers is the removal of annoying punctuation from the content, and reducing words to their lemmas to reduce the number of words that we are examining. Finally, we'll divide the articles into training and testing sets and separate our predictor, the words in the content, and the target, the publisher."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"def text_cleaner(text):\n",
" # Visual inspection identifies a form of punctuation spaCy does not\n",
" # recognize: the double dash '--'. Better get rid of it now!\n",
" text = re.sub(r'--',' ',text)\n",
" text = re.sub(\"[\\[].*?[\\]]\", \"\", text)\n",
" text = ' '.join(text.split())\n",
" return text"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
title
\n",
"
publication
\n",
"
author
\n",
"
content
\n",
"
\n",
" \n",
" \n",
"
\n",
"
34296
\n",
"
Lawmakers to Trump: Turn over transcript of me...
\n",
"
Washington Post
\n",
"
Elise Viebeck
\n",
"
A growing number of Republican and Democratic ...
\n",
"
\n",
"
\n",
"
39572
\n",
"
Man charged with attacking Uber driver sues dr...
\n",
"
New York Post
\n",
"
Associated Press
\n",
"
COSTA MESA, Calif. — A Southern California man...
\n",
"
\n",
"
\n",
"
5253
\n",
"
Democratic Response To Trump’s Address To Cong...
\n",
"
NPR
\n",
"
NaN
\n",
"
Following President Trump’s address to a joint...
\n",
"
\n",
"
\n",
"
48670
\n",
"
Trump tries to salvage travel ban amid numerou...
\n",
"
Guardian
\n",
"
Ben Jacobs
\n",
"
Donald Trump scrambled to salvage his controve...
\n",
"
\n",
"
\n",
"
32448
\n",
"
Influential conservative group: Trump, DeVos s...
\n",
"
Washington Post
\n",
"
Emma Brown
\n",
"
A policy manifesto from an influential conserv...
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" title publication \\\n",
"34296 Lawmakers to Trump: Turn over transcript of me... Washington Post \n",
"39572 Man charged with attacking Uber driver sues dr... New York Post \n",
"5253 Democratic Response To Trump’s Address To Cong... NPR \n",
"48670 Trump tries to salvage travel ban amid numerou... Guardian \n",
"32448 Influential conservative group: Trump, DeVos s... Washington Post \n",
"\n",
" author content \n",
"34296 Elise Viebeck A growing number of Republican and Democratic ... \n",
"39572 Associated Press COSTA MESA, Calif. — A Southern California man... \n",
"5253 NaN Following President Trump’s address to a joint... \n",
"48670 Ben Jacobs Donald Trump scrambled to salvage his controve... \n",
"32448 Emma Brown A policy manifesto from an influential conserv... "
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Remove annoying punctuation from the articles\n",
"articles['content'] = articles.content.map(lambda x: text_cleaner(str(x)))\n",
"articles.head()"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"lemmatizer = WordNetLemmatizer()\n",
"\n",
"# Reduce all text to their lemmas\n",
"for article in articles['content']:\n",
" article = lemmatizer.lemmatize(article)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# Identify predictor and target variables\n",
"X = articles['content']\n",
"y = articles['publication']\n",
"\n",
"# Create training and testing sets\n",
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Tf-idf Vectorization\n",
"\n",
"The first types of features that we are going to add are the most useful words in our dataset. Now how are we going to determine which words are deemed the most \"useful\"? With TF-IDF vectorizer, of course.\n",
"\n",
"TF tracks the term frequency, or how often each word appears in all articles of text, while idf (or Inverse Document Frequency) is a value that places less weight on variables that occur too often and lose their predictive power. Put together, it's a tool that allows us to assign an importance value to each word in the entire dataset based on frequency in each row and throughout the database.\n",
"\n",
"These are the parameters that will be used for TF-IDF\n",
"1. All words that appear in over half of the articles will be thrown out of the dataframe\n",
"2. Only words that occur more than 5 times will be tracked\n",
"3. Only the top 150 features (words) will be kept\n",
"4. Stop words will be ignored (like, as, the)\n",
"5. Cases will be ignored\n",
"6. Shorter and longer articles will be treated equally\n",
"7. Add 1 to document frequency in case we have to divide by 0"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of features: 150\n"
]
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"\n",
"# Parameters for TF-idf vectorizer\n",
"vectorizer = TfidfVectorizer(max_df=0.5,\n",
" min_df=5, \n",
" max_features=150, \n",
" stop_words='english', \n",
" lowercase=True, \n",
" use_idf=True,\n",
" norm=u'l2',\n",
" smooth_idf=True\n",
" )\n",
"\n",
"#Applying the vectorizer\n",
"X_tfidf=vectorizer.fit_transform(X)\n",
"print(\"Number of features: %d\" % X_tfidf.get_shape()[1])\n",
"\n",
"#splitting into training and test sets\n",
"X_train_tfidf, X_test_tfidf, y_train, y_test = train_test_split(X_tfidf, y, test_size=0.25, random_state=42)\n",
"\n",
"#Removes all zeros from the matrix\n",
"X_train_tfidf_csr = X_train_tfidf.tocsr()\n",
"\n",
"#number of paragraphs\n",
"n = X_train_tfidf_csr.shape[0]\n",
"\n",
"#A list of dictionaries, one per paragraph\n",
"tfidf_bypara = [{} for _ in range(0,n)]\n",
"\n",
"#List of features\n",
"terms = vectorizer.get_feature_names()\n",
"\n",
"#for each paragraph, lists the feature words and their tf-idf scores\n",
"for i, j in zip(*X_train_tfidf_csr.nonzero()):\n",
" tfidf_bypara[i][terms[j]] = X_train_tfidf_csr[i, j]\n",
"\n",
"# Normalize the dataset \n",
"X_norm = normalize(X_train_tfidf)\n",
"\n",
"# Convert from tf-idf matrix to dataframe\n",
"X_normal = pd.DataFrame(data=X_norm.toarray())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Phrase count with spacy\n",
"\n",
"The second set of variables that we will be creating are counters of how often each publishers makes use of each part of speech, meaning adverbs, verbs, nouns, adjectives, as well as article length."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"# Instantiating spaCy\n",
"nlp = spacy.load('en')\n",
"X_train_words = []\n",
"\n",
"for row in X_train:\n",
" # Processing each row for tokens\n",
" row_doc = nlp(row)\n",
" # Calculating length of each sentence\n",
" sent_len = len(row_doc) \n",
" # Initializing counts of different parts of speech\n",
" advs = 0\n",
" verb = 0\n",
" noun = 0\n",
" adj = 0\n",
" for token in row_doc:\n",
" # Identifying each part of speech and adding to counts\n",
" if token.pos_ == 'ADV':\n",
" advs +=1\n",
" elif token.pos_ == 'VERB':\n",
" verb +=1\n",
" elif token.pos_ == 'NOUN':\n",
" noun +=1\n",
" elif token.pos_ == 'ADJ':\n",
" adj +=1\n",
" # Creating a list of all features for each sentence\n",
" X_train_words.append([row_doc, advs, verb, noun, adj, sent_len])\n",
"\n",
"# Create dataframe with count of adverbs, verbs, nouns, and adjectives\n",
"X_count = pd.DataFrame(data=X_train_words, columns=['BOW', 'ADV', 'VERB', 'NOUN', 'ADJ', 'sent_length'])\n",
"\n",
"# Change token count to token percentage\n",
"for column in X_count.columns[1:5]:\n",
" X_count[column] = X_count[column] / X_count['sent_length']\n",
"\n",
"# Normalize X_count\n",
"X_counter = normalize(X_count.drop('BOW',axis=1))\n",
"X_counter = pd.DataFrame(data=X_counter)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
0
\n",
"
1
\n",
"
2
\n",
"
3
\n",
"
4
\n",
"
0
\n",
"
1
\n",
"
2
\n",
"
3
\n",
"
4
\n",
"
...
\n",
"
140
\n",
"
141
\n",
"
142
\n",
"
143
\n",
"
144
\n",
"
145
\n",
"
146
\n",
"
147
\n",
"
148
\n",
"
149
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
0.000328
\n",
"
0.000705
\n",
"
0.000623
\n",
"
0.000393
\n",
"
0.999999
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
...
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.0
\n",
"
0.197798
\n",
"
0.0
\n",
"
0.334632
\n",
"
0.00000
\n",
"
0.000000
\n",
"
\n",
"
\n",
"
1
\n",
"
0.000110
\n",
"
0.000327
\n",
"
0.000275
\n",
"
0.000125
\n",
"
1.000000
\n",
"
0.059944
\n",
"
0.062871
\n",
"
0.0
\n",
"
0.048974
\n",
"
0.0
\n",
"
...
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.452318
\n",
"
0.0
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.00000
\n",
"
0.000000
\n",
"
\n",
"
\n",
"
2
\n",
"
0.000582
\n",
"
0.001136
\n",
"
0.000554
\n",
"
0.000360
\n",
"
0.999999
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
...
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.00000
\n",
"
0.000000
\n",
"
\n",
"
\n",
"
3
\n",
"
0.000137
\n",
"
0.000630
\n",
"
0.000425
\n",
"
0.000182
\n",
"
1.000000
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
...
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.00000
\n",
"
0.000000
\n",
"
\n",
"
\n",
"
4
\n",
"
0.000128
\n",
"
0.000548
\n",
"
0.000411
\n",
"
0.000183
\n",
"
1.000000
\n",
"
0.000000
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
...
\n",
"
0.0
\n",
"
0.118669
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.0
\n",
"
0.000000
\n",
"
0.05139
\n",
"
0.674409
\n",
"
\n",
" \n",
"
\n",
"
5 rows × 155 columns
\n",
"
"
],
"text/plain": [
" 0 1 2 3 4 0 1 2 \\\n",
"0 0.000328 0.000705 0.000623 0.000393 0.999999 0.000000 0.000000 0.0 \n",
"1 0.000110 0.000327 0.000275 0.000125 1.000000 0.059944 0.062871 0.0 \n",
"2 0.000582 0.001136 0.000554 0.000360 0.999999 0.000000 0.000000 0.0 \n",
"3 0.000137 0.000630 0.000425 0.000182 1.000000 0.000000 0.000000 0.0 \n",
"4 0.000128 0.000548 0.000411 0.000183 1.000000 0.000000 0.000000 0.0 \n",
"\n",
" 3 4 ... 140 141 142 143 144 145 146 \\\n",
"0 0.000000 0.0 ... 0.0 0.000000 0.000000 0.0 0.0 0.197798 0.0 \n",
"1 0.048974 0.0 ... 0.0 0.000000 0.452318 0.0 0.0 0.000000 0.0 \n",
"2 0.000000 0.0 ... 0.0 0.000000 0.000000 0.0 0.0 0.000000 0.0 \n",
"3 0.000000 0.0 ... 0.0 0.000000 0.000000 0.0 0.0 0.000000 0.0 \n",
"4 0.000000 0.0 ... 0.0 0.118669 0.000000 0.0 0.0 0.000000 0.0 \n",
"\n",
" 147 148 149 \n",
"0 0.334632 0.00000 0.000000 \n",
"1 0.000000 0.00000 0.000000 \n",
"2 0.000000 0.00000 0.000000 \n",
"3 0.000000 0.00000 0.000000 \n",
"4 0.000000 0.05139 0.674409 \n",
"\n",
"[5 rows x 155 columns]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Combine tf-idf matrix and phrase count matrix\n",
"features = pd.concat([X_counter,X_normal], ignore_index=False, axis=1)\n",
"features.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And now we have our list of features. It doesn't look anything like our original dataset, now does it? That's because our sentences have been transformed into numbers to feed into our clustering algorithms and predictive models.\n",
"\n",
"# Clustering\n",
"\n",
"Now it's finally time for some unsupervised machine learning. Each article has been binarized to 1s and 0s, and it's time to determine if we can determine if each publisher has a different method for publication."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAmsAAAFNCAYAAABfUShSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3Xd4VHXaxvHvA5HFgoKCBVDAuqAiaMAOIopYsSKoa1vXsqsoi6Io9s5asLuuBduqKBaaooshIrYEEFdwQcRCNyiKioLI8/7xO/NmiGmETM6Z5P5c11yZOXNm5plD3Nz7q+buiIiIiEgy1Yu7ABEREREpm8KaiIiISIIprImIiIgkmMKaiIiISIIprImIiIgkmMKaiIiISIIprIlUMzO7xsyeqoHPaW1mbmY50eMJZnZWpj+3JlTndzGzYWZ2QxVe52a2fXXUUMb7729mMzP1/qV8Xka/T1WZ2eVm9nCG3vsLMzuojOeq9HshEgeFNZG1ZGY/pt1Wm9nPaY9PrubPGmZmK0t85rTq/IyqSguLU0ocbxrV/EUl36dGwm3SuPtEd98pE++d1OBuZgeY2bz0Y+5+k7snrlaRJFFYE1lL7r5R6gZ8BRyZduzpDHzkkPTPdPfdMvAZ62JDM9sl7fFJwOdxFSMiUtsorIlkRgMze8LMfjCz6WaWm3rCzJqb2QgzKzKzz82sXzV+7nZm9oGZfW9mr5jZpmmfe1RUy3dRy0vb6PgZZjYq7bzZZjY87fFcM+tQzmc+CZyW9vhU4In0E8r6zmbWE7gcOLGUVsNWZjYpuoavm1nTir5L9FxHM5sSve45oGFZhZvZ9maWH12vJdH56Q4ys0/NbKmZ3WdmFr2unpkNNrMvzezr6N96k+i5x81sQHS/RdT6+Ne0z/vWgjVamaIuu4vN7KOonufMrGHa8wPNbKGZLTCzs8rq1jSzG4H9gXuja3pvRd8net2ZZvZJ9Nw4M2tVznUr7/p/YWaDzGxG9F6PmVlDM9sQeBVobsWtxM3TW1atuLX2jOj3bqmZnWtmnaLr8l369zGz7czsTTP7Jvr3e9rMGpdVdznfp5GZ5ZnZ3enXRCQpFNZEMuMo4FmgMTASuBfCH3lgFDANaAF0By4ys0Oq6XNPBc4EmgOrgLujz90ReAa4CGgGjAVGmVkDIB/YPwogWwHrAftGr9sW2Aj4qJzPfAroY2b1oz/ajYD3U0+W953d/TXgJuC5UloNTwLOADYHGgAXV/Rdou/zMiFAbgo8DxxXTu3XA68DTYCWwD0lnj8C6ATsBvQGUv9Op0e3bkDqGqVCRD5wQHS/KzAn+gnQBZjoZe/z1xvoCbQB2kefkQq1fwcOArZPe7/fcfcrgInA+dE1Pb+i72NmRxNC87GEazqRcI1/p4LfpZSTo/feDtgRGOzuPwGHAgvSWokXlPE19gR2AE4EhgJXRN99Z6C3maW+vwE3E37f2wJbA9eUdW3K+D6bAeOBSe7er5x/G5HYKKyJZMbb7j7W3X8jBIdUCOkENHP369x9pbvPAf4F9CnnvS6OWhRSt8fLOfdJd/84+sN4JeEPW33CH70x7v6Gu/8K3AasD+wT1fAD0IEQAsYB883sj9Hjie6+upzPnAfMJPwxPY0SrWpV/M4Aj7n7LHf/GRge1Ud53wXYixA2h7r7r+7+AlBQzmf8CrQCmrv7L+7+donnb3H379z9KyAvrYaTgTvcfY67/wgMIgTWHNLCLyGcDSEKv4TrmV9OPXe7+wJ3/5YQcFOf1zu6HtPdfTlwbTnvUZ6yvs85wM3u/om7ryIE6A5ltK6Vd/1T7nX3udH3uBHou5Z1Xh/9e7wO/AQ84+5fu/t8QpDsCODus6M6Vrh7EXAH5QTZUjQn/Hs87+6D17JGkRqjsCaSGYvS7i8HGkZ/yFsRuoH+P3wRWjS2KOe9bnP3xmm308o5d27a/S8JwaUp4Y/Sl6knovA1l9DSBcWtQV2i+xMIf/QqChcpTxBagfoSWtrSVeU7w++v4UbR/fK+S3NgfonWkS8p20BC68wHUbfemVWpIbqfA2zh7p8BPxKC0P7AaGCBme1ExdezvM9L/7dNv782ynr/VsBdaf8+3xKuSwt+r6LfpZL1fRm9Zm0sTrv/cymPNwIws83N7Fkzm29mywi/e02pvMMJQfPBtaxPpEYprInUrLnA5yXCVyN3P6ya3n/rtPvbEFqOlgALCH+QAYjG5WwNzI8OpcLa/tH9fNYurI0g/OGb4+4lw1FF33ltu53K+y4LgRYlxh1tU9Ybufsid/+LuzcntC7dX9o4sIpqiD5jFcWhIh84HmgQtQblE7qomwAfVuL9S1pI6KZN2bqsEyNre03nAueU+Dda393fKeXcin6XSta3TfSaqtRVkZuj92zv7hsDpxBCZmX9C3gNGBuNqRNJJIU1kZr1AbDMzC41s/WjcV67mFmnanr/U8ysnZltAFwHvBB1xQ4HDjez7ma2HjAAWAGk/hjnE8Zfre/u8whdTT2BzYCpFX1o1O16IFDaEgwVfefFQOuo27Ayyvsu7xJCUz8zyzGzY4HOZb2RmZ1gZqkQtJTwh/+3StTwDNDfzNqY2UYUj7tbFT2fD5wPvBU9ngBcQOger8z7lzQcOMPM2kb/tldVcP5iwli6ynoQGGRmOwOY2SZmdkI5tZT3uwTwNzNraWGCy+VAauLGYmAziyZjVINGhFbM78ysBXBJFd7jfEI3/mgzW7+a6hKpVgprIjUo+kN9JKGL7HNCq9fDQHl/vAbamuusLSnn3CeBYYTuroZAv+hzZxJaHe6JPvNIwpIjK6PnZxH+6E2MHi8jDIyfVNlw4e6FURfg2n7n56Of31iJNdvK+Jwyv0v0fY4ldMkuJYyverGct+sEvG9mPxImglzo7pVZduRRwrV+K/pOvxDCWEo+IUikwtrbwAZpj9eKu79KmCySB8wmhFIIIak0dwHHR7Mp767E+78E3Ao8G3UnfkyYDFDaueX+LkX+TZi4MSe63RC99n+EoDsn6nJd2+7Rkq4Fdge+B8ZQ/r91qaIu87MJrYuvWNoMXJGkME18ERHJLtGs24+BP6S15iWChcWQz3L3/8Rdi0htoZY1EZEsYGbHRMuTNCG0go1KWlATkcxQWBMRyQ7nAEXAZ4RxdefFW46I1BR1g4qIiIgkmFrWRERERBJMYU1EREQkwXLiLqC6NG3a1Fu3bh13GSIiIiIVmjx58hJ3b1aZc2tNWGvdujWFhYVxlyEiIiJSITMrbyu8NagbVERERCTBFNZEREREEkxhTURERCTBFNZEREREEkxhTURERCTBFNZEREREEkxhTURERCTBFNYqYcgQyMtb81heXjguIiIikkkKa5XQqRP07l0c2PLywuNOneKtS0RERGq/WrODQSZ16wbPPQe9ekHfvvDiizB8eDguIiIikklqWauk3Fz47Td46CE491wFNREREakZCmuVNHky1K8f7g8d+vsxbCIiIiKZoLBWCakxai+9BPvuC/XqwQknKLCJiIhI5imsVUJBQRij1r073H8//PQT7LNPOC4iIiKSSQprlTBwYPEYtfbt4YILYPRojVsTERGRzFNYq4JrroEttoC//jVMOhARERHJFIW1KthkE7j9digshIcfjrsaERERqc0U1qqob9/QDTpoEBQVxV2NiIiI1FYKa1VkBvfeCz/8AJddFnc1IiIiUlsprK2Ddu2gf3949FF45524qxEREZHaSGFtHV11FbRsCX/7G6xaFXc1IiIiUtsorK2jjTaCO++EDz+EBx6IuxoRERGpbRTWqsFxx0GPHjB4MCxaFHc1IiIiUpsorFUDM7jnHvjlF7jkkrirERERkdpEYa2a7LhjCGpPPQVvvRV3NSIiIlJbKKxVo8svh1atws4Gv/4adzUiIiJSGyisVaMNNoC774bp08NPERERkXWV0bBmZj3NbKaZzTaz3y0da2atzGy8mX1kZhPMrGV0vIOZvWtm06PnTsxkndXpyCPh8MPD/qHz58ddjYiIiGS7jIU1M6sP3AccCrQD+ppZuxKn3QY84e7tgeuAm6Pjy4FT3X1noCcw1MwaZ6rW6mQWWtVWrYIBA+KuRkRERLJdJlvWOgOz3X2Ou68EngV6lTinHTA+up+Xet7dZ7n7p9H9BcDXQLMM1lqttt027Bn63HMwfnzF54uIiIiUJZNhrQUwN+3xvOhYumnAcdH9Y4BGZrZZ+glm1hloAHyWoTozYuBA2G67sLPBihVxVyMiIiLZKpNhzUo55iUeXwx0NbOpQFdgPvD/mzaZ2VbAk8AZ7r76dx9gdraZFZpZYVFRUfVVXg0aNgxrr82cCXfcEXc1IiIikq0yGdbmAVunPW4JLEg/wd0XuPux7t4RuCI69j2AmW0MjAEGu/t7pX2Auz/k7rnuntusWfJ6SQ89FI45Bq6/Hr76Ku5qREREJBtlMqwVADuYWRszawD0AUamn2BmTc0sVcMg4NHoeAPgJcLkg+czWGPGDR0afl50Ubx1iIiISHbKWFhz91XA+cA44BNguLtPN7PrzOyo6LQDgJlmNgvYArgxOt4b6AKcbmYfRrcOmao1k7bZBq68El56CV59Ne5qREREJNuYe8lhZNkpNzfXCwsL4y6jVCtXQvv2YTmPjz8O49lERESk7jKzye6eW5lztYNBDWjQAO67Dz77DIYMibsaERERySYKazWke3c48US4+WaYMyfuakRERCRbKKzVoNtvh5wc6NcPaknvs4iIiGSYwloNatEi7Bk6ZgyMHFnh6SIiIiIKazWtXz/YeWe48EJYvjzuakRERCTpFNZq2Hrrwf33w5dfwk03xV2NiIiIJJ3CWgy6dIFTToF//ANmzYq7GhEREUkyhbWY/OMfYb2188/XZAMREREpm8JaTLbcEm64Ad54A154Ie5qREREJKkU1mJ03nnQoQP07w8//hh3NSIiIpJECmsxyskJkw3mz4frrou7GhEREUkihbWY7b03nHkm3HknTJ8edzUiIiKSNAprCXDLLdCoEfztb5psICIiImtSWEuAZs3Cmmv5+fDMM3FXIyIiIkmisJYQf/kL5ObCgAHw/fdxVyMiIiJJobCWEPXrwwMPwOLFcPXVcVcjIiIiSaGwliC5uXDOOXDPPTBtWtzViIiISBIorCXMjTfCppuGyQarV8ddjYiIiMRNYS1hNt0Ubr0VJk2CJ56IuxoRERGJm8JaAp1+elh/beBAWLo07mpEREQkTgprCVSvXtjZ4Jtv4Ior4q5GRERE4qSwllAdOoRxaw8+CJMnx12NiIiIxEVhLcGuvx423xz++ldNNhAREamrFNYSbJNN4Lbb4IMP4OGH465GRERE4qCwlnAnnwxdusCgQbBkSdzViIiISE1TWEs4M7jvvrAF1aBBcVcjIiIiNU1hLQvssgtcdFHoCn3vvbirERERkZqksJYlrr4amjcPkw1++y3uakRERKSmKKxliUaN4I47YOrUsOG7iIiI1A0Ka1mkd2846CAYPBgWL467GhEREakJCmtZxAzuvReWLw9bUYmIiEjtp7CWZXbaCS6+OGzyPnFi3NWIiIhIpimsZaErroBttgmTDX79Ne5qREREJJMU1rLQhhvC0KHw8cehW1RERERqL4W1LHX00XDooWFJjwUL4q5GREREMkVhLUuZwT33wMqVMGBA3NWIiIhIpiisZbHttoP99oNnn4Xx44uP5+XBkCHx1SUiIiLVJ6Nhzcx6mtlMM5ttZpeV8nwrMxtvZh+Z2QQza5n23Glm9ml0Oy2TdWaziy+GevXgjDNCK1teXliPrVOnuCsTERGR6pCxsGZm9YH7gEOBdkBfM2tX4rTbgCfcvT1wHXBz9NpNgauBPYHOwNVm1iRTtWaznj3hhhtg7lzYZ58Q1IYPh27d4q5MREREqkMmW9Y6A7PdfY67rwSeBXqVOKcdkOrAy0t7/hDgDXf/1t2XAm8APTNYa1YbNAj23BMmT4aGDUP3qIiIiNQOmQxrLYC5aY/nRcfSTQOOi+4fAzQys80q+VrM7GwzKzSzwqKiomorPNvk5cFnn8Gxx8L8+bDzzjBqVNxViYiISHXIZFizUo55iccXA13NbCrQFZgPrKrka3H3h9w9191zmzVrtq71ZqXUGLXhw2HEiLCzwS+/wFFHhVmiK1fGXaGIiIisi0yGtXnA1mmPWwJrrAjm7gvc/Vh37whcER37vjKvlaCgYM0xaqecAqNHh/Frd9wB++8Pn38eb40iIiJSdZkMawXADmbWxswaAH2AkeknmFlTM0vVMAh4NLo/DuhhZk2iiQU9omNSwsCBv59McMghMGkSvPACzJwJHTvCSy/FU5+IiIism4yFNXdfBZxPCFmfAMPdfbqZXWdmR0WnHQDMNLNZwBbAjdFrvwWuJwS+AuC66JisheOOgylTYMcdw3i2fv1gxYq4qxIREZG1Ye6/GwqWlXJzc72wsDDuMhJp5Uq49NKwn+gee8Bzz2nGqIiISJzMbLK751bmXO1gUAc0aAB33gkvvxxmjXbsGMa5iYiISPIprNUhvXrBhx+GpT1OPBHOOy/MHBUREZHkUlirY1q1grfegksugQcfhL32glmz4q5KREREyqKwVgett17Y6H30aJg3L4xj+/e/465KRERESqOwVocdfnjoFu3QAU4+Gf7yF1i+PO6qREREJJ3CWh3XsmXYBWHQIHj44bDH6CefxF2ViIiIpCisCTk5cNNN8NprsHgx5ObC44/HXZWIiIiAwpqkOeSQ0C3auTOcfnq4/fRT3FWJiIjUbQprsobmzeE//4GrrgqbwnfqBB9/HHdVIiIidZfCmvxO/fpw7bUhtH37bQhsjzwCtWSzCxERkayisCZlOvDA0C26775w1lnwpz/BDz/EXZWIiEjdorAm5dpySxg3Dq6/Hp55Jkw+mDYt7qpERETqDoU1qVD9+jB4MLz5Jvz4Y1je48EH1S0qIiJSExTWpNK6dg3dot26hX1F+/SBZcvirkpERKR2U1iTtdKsGYwZA7fcAiNGwO67w+TJcVclIiJSeymsyVqrVw8uvRTy82HFCthnH7j3XnWLioiIZILCmlTZvvuGbtEePeCCC+D44+G77+KuSkREpHZRWJN1stlmMHIk3HZb+NmxI3zwQdxViYiI1B4Ka7LOzGDAAJg4MXSF7rcf3HmnukVFRESqg8KaVJu99oKpU+Hww+Hvf4edd4ZXXlnznLw8GDIknvpERESykcKaVKsmTeDFF+Guu2DWLDj22DD5AEJQ6907bF8lIiIilaOwJtXODPr1g/fegy22CJMPuncPQW348LBOm4iIiFSOwppkTG4ufPIJtG0bdj/YaCPYcce4qxIREckuCmuSUVOmQFERHHkkfPFFCG4vvxx3VSIiItlDYU0yJjVGbfjwsKzH44/Dzz/DMceE7aqWL4+7QhERkeRTWJOMKShYc4zaqaeGraq6dAkbwefmwrRp8dYoIiKSdAprkjEDB/5+MkGPHmGbqjfeCLsddO4MQ4fC6tXx1CgiIpJ0CmsSi4MOgo8+gp49oX//sDbb4sVxVyUiIpI8CmsSm6ZNw2SD+++HCROgfXt49dW4qxIREUkWhTWJlVmYbFBYGNZkO+wwuOgi+OWXuCsTERFJhkqHNTOrb2bNzWyb1C2ThUndsvPOYQP4fv3C7gd77gkzZsRdlYiISPwqFdbM7AJgMfAGMCa6jc5gXVIHNWwYgtqYMbBwIeyxBzzwgDaEFxGRuq2yLWsXAju5+87uvmt0a5/JwqTuOuywMPmga1f461/h6KNhyZK4qxIREYlHZcPaXOD7TBYikm7LLWHsWLjzTnjttTD5YPz4uKsSERGpeZUNa3OACWY2yMz+nrplsjCRevXCZIP334dNNoGDD4ZLL4WVK+OuTEREpOZUNqx9RRiv1gBolHYTybgOHWDyZDj7bBgyBPbZB2bNirsqERGRmpFTmZPc/VoAM2sUHvqPlXmdmfUE7gLqAw+7+y0lnt8GeBxoHJ1zmbuPNbP1gIeB3aMan3D3myv3laQ22mCDsEXVIYfAWWfB7rvD3XfDGWeE5T9ERERqq8rOBt3FzKYCHwPTzWyyme1cwWvqA/cBhwLtgL5m1q7EaYOB4e7eEegD3B8dPwH4g7vvCuwBnGNmrSv3laQ2O+aYsJ9o587w5z9Dnz6wdGncVYmIiGROZbtBHwL+7u6t3L0VMAD4VwWv6QzMdvc57r4SeBboVeIcBzaO7m8CLEg7vqGZ5QDrAyuBZZWsVWq5li3D3qI33wwvvhi6SSdOjLsqERGRzKhsWNvQ3fNSD9x9ArBhBa9pQZhFmjIvOpbuGuAUM5sHjAUuiI6/APwELCSMl7vN3b+tZK1SB9SvD5ddBpMmwXrrwQEHwFVXwapVcVcmIiJSvSo9G9TMrjSz1tFtMPB5Ba8pbSRRyeVN+wLD3L0lcBjwpJnVI7TK/QY0B9oAA8xs2999gNnZZlZoZoVFRUWV/CpSm3TuDFOnwp/+BNdfD126wOcV/WaKiIhkkcqGtTOBZsCLwEvR/TMqeM08YOu0xy0p7uZM+TMwHMDd3wUaAk2Bk4DX3P1Xd/8amATklvwAd3/I3XPdPbdZs2aV/CpS2zRqBMOGwTPPwPTpoVv03/+OuyoREZHqUamw5u5L3b2fu+/u7h3d/UJ3r2hYdwGwg5m1MbMGhAkEI0uc8xXQHcDM2hLCWlF0/EALNgT2Av5X+a8ldVGfPmHywS67wMknw6mnwjKNdBQRkSxXblgzs6HRz1FmNrLkrbzXuvsq4HxgHPAJYdbndDO7zsyOik4bAPzFzKYBzwCnu7sTZpFuRJh9WgA85u4frcP3lDqidWvIz4drroGnn4aOHcOiuiIiItnKvJxdss1sD3efbGZdS3ve3fMzVtlays3N9cLCwrjLkASZNCm0sM2bB9deGyYk1K8fd1UiIiJgZpPd/XdDvEpTbsuau0+O7nZw9/z0G9BhXQsVyaR994UPP4Tjj4fBg+HAA2Hu3IpfJyIikiSVnWBwWinHTq/GOkQyonHjMPFg2LCwZdVuu8ELL8RdlYiISOVVNGatr5mNArYtMV4tD/imZkoUWTdmcNppoZVtu+3ghBPCkh9jx655Xl5e2HtUREQkSSraG/QdwsK0TYHb047/AGjAv2SV7bcP49iuvhpuuQWOOgruuw/OOScEtd69YfjwuKsUERFZU7kTDOD/9/gc5+4H1UxJVaMJBrI2UuFsyZIwtu1//4Pnn4du3eKuTERE6oJqm2AA4O6/AcvNbJN1rkwkIbp1CwFt551Da9v338OoUTB/ftyViYiIrKmyEwx+Af5rZo+Y2d2pWyYLE8m0jz6CxYvh3HPDkh533QVt2sDZZ8Ps2XFXJyIiElQ2rI0BrgTeAian3USyUvoYtQcegFdfDTNHDz0UnngCdtoJTjoJ/vvfuCsVEZG6rrLbTT1O2GEgFdL+HR0TyUoFBSGopcaodesWlvTYd9+wEfyAAaFbtH37MBHhvffirVdEROquCicYAJjZAcDjwBeAETZoP83d38pkcWtDEwykun37LdxzT+geXbo0BLorrgiL65rFXZ2IiGSzap1gELkd6OHuXd29C3AIcGdVCxTJBptuGpb5+PJLuO22MCHhoINgr73glVdg9eq4KxQRkbqgsmFtPXefmXrg7rOA9TJTkkiyNGoUukXnzIEHH4SiIjj66NBF+vTTsGpV3BWKiEhtVtmwVhjNBD0guv0LTTCQOqZhw7CA7qxZ8NRT4dgpp8COO8I//wm//BJvfSIiUjtVNqydB0wH+gEXAjOAczJVlEiS5eTAySeHpT9efhmaNg3Lf2y7Ldx+O/z4Y9wViohIbVLZsHauu9/h7se6+zHufichwInUWfXqQa9e8P778J//QNu2cPHF0KoVXHttmKAgIiKyriob1k4r5djp1ViHSNYyg+7dYfx4ePdd2G8/uOaaENouuQQWLoy7QhERyWblhjUz62tmo4A2ZjYy7TYB+KZGKhTJIqmZoh99FNZnu+OOsCvCeeeF9dtERETWVrnrrJlZK6ANcDNwWdpTPwAfuXti5sFpnTVJotmzYcgQGDYsLPXRty9cdlnYk1REROqualtnzd2/dPcJwEHARHfPBxYCLQmL44pIObbfHh56KLSq9esHL74Iu+wCxx4bdlEQERGpSGXHrL0FNDSzFsB44AxgWKaKEqltWrQIXaJffglXXhn2Ju3cGXr0gAkToBIbiYiISB1V2bBm7r4cOBa4x92PAdplriyR2qlpU7juuhDabr01jG3r1i3sSTp6tEKbiIj8XqXDmpntDZwMjImO5WSmJJHab+ONYeDA0D16332wYAEceSR06AAnnRSWAkmXlxfGvomISN1T2bB2ETAIeMndp5vZtkBe5soSqRvWXx/++lf49FN4/HFYuRKeeQYOOSSs2bZiRQhqvXtDp05xVysiInEodzZoNtFsUKkNVq8OuyJcdlkIcOuvH7pG774b/vKXuKsTEZHqUm2zQc1saPRzVIl11kaa2cjqKFZEitWrF2aKzpwZtrT6+efQ2nb22dCxYwhtS5bEXaWIiNSkirpBn4x+3gbcXspNRDJgwgQYNy7MHG3SBC64IAS5Cy+E5s3h+ONhzBhYlZiVDkVEJFPKnSTg7pOjn/lm1iy6X1QThYnUVakxasOHh5mi3boVP95sM3jsMXjqKRgxArbaCv70JzjjDPjjH+OuXEREMqGiblAzs2vMbAnwP2CWmRWZ2VU1U55I3VNQUBzUIPwcPjwcb98e7rwT5s8PC+zm5sLtt4dN5PfeOyzA+/338dYvIiLVq6LtpvoDhwFnu/vn0bFtgQeA19z9zhqpshI0wUDqqkWLQkvbY4/BjBlhUsKxx4bWtm7dQvepiIgky9pMMKgorE0FDnb3JSWONwNed/eO61RpNVJYk7rOHQoLQ2h75hn47jto1QpOOw1OPz1sKC8iIslQbbNBgfVKBjX4/3Fr61WlOBHJDLOwFtv998PChSGw7bQTXH89bLttaGV74gn46ae4KxURkbVRUVhbWcXnRCRGDRtCnz5hRumXX8INN8C8eaGVbaut4KyzYNIkbW8lIpINKuoG/Q0o7f+HG9DQ3RNoL5g6AAAY7ElEQVTTuqZuUJHyucPbb4du0uHDQwvbjjuGLtJTTw2bzYuISM2otm5Qd6/v7huXcmuUpKAmIhUzg/33h0cfDZMSHnsMttwSLr8cttkGevaE556DX36Ju1IREUmneWIiddBGG4UWtfx8mD07BLYZM0LXafPmcP75MHmyuklFRJJAYU2kjttuuzAJ4fPP4fXX4dBD4ZFHwhpuu+0W1nX7+utw7pAhYdHedHl54biIiGRGRsOamfU0s5lmNtvMLivl+W3MLM/MpprZR2Z2WNpz7c3sXTObbmb/NbOGmaxVpK6rXx8OPhiefjrMJn3wQdhgA/j738N4tqOPDvuU9u5dHNhSuy106hRv7SIitVm5EwzW6Y3N6gOzgIOBeUAB0NfdZ6Sd8xAw1d0fMLN2wFh3b21mOcAU4E/uPs3MNgO+c/ffyvo8TTAQyYwZM2DYMHjyyTDWrXFjWLEiTEoYMWLN3RZERKRyqnOdtXXRGZjt7nPcfSXwLNCrxDkObBzd3wRYEN3vAXzk7tMA3P2b8oKaiGROu3ahm3PuXBg1Cg48MIS1f/4TcnJg2rTiblIREal+mQxrLYC5aY/nRcfSXQOcYmbzgLHABdHxHQE3s3FmNsXMBmawThGphJwcOOKIMPmgSZMwe3TJEujfP0xKOPJIeP55zSYVEalumQxrVsqxkn2ufYFh7t6SsAfpk2ZWD8gB9gNOjn4eY2bdf/cBZmebWaGZFRYVFVVv9SLyO6kxas8/D6++GiYkNGkSjk2dGn5utRWcey68845mk4qIVIdMhrV5wNZpj1tS3M2Z8mdgOIC7vws0BJpGr8139yXuvpzQ6rZ7yQ9w94fcPdfdc5s1a5aBryAi6QoK1hyj1q1bGLfWoUPYKeGNN0IL25NPwr77hkV3r78evvgi1rJFRLJaJicY5BAmGHQH5hMmGJzk7tPTznkVeM7dh5lZW2A8oau0cXR/P8K2Vq8Bd7r7mLI+TxMMRJLjhx/gxRfDXqR5eaGFrUuXsN3V8cfDxhtX/B4iIrVZIiYYuPsq4HxgHPAJMNzdp5vZdWZ2VHTaAOAvZjYNeAY43YOlwB2EgPchMKW8oCYiydKoUQhm48eHVrUbbwwzSf/8Z9hiCzjppLBv6W+aNiQiUqGMtazVNLWsiSSbO3zwQWhte+YZWLo0jG87+eQQ7HbZJe4KRURqTiJa1kRE0pnBnnvCffeFRXdHjIDOnWHoUNh1V9h993B/8eK4KxURSRaFNRGpcX/4Axx7LLz8MixYAHffHXZQ6N8/7JagZUBERIoprIlIrJo1gwsuCDNNp0+Hiy/WMiAiIukU1kQkMdq1g1tuCcuAvP56WIRXy4CISF2nsCYiiZPaVD61H+mwYbD11nDVVdCmDXTtCo8+CsuWxV2piEjmKayJSKKllgF5883ylwG55Zawplu6vLywr6mISDZTWBORrNGqFVx+Ofzvf/Dee3DmmfDaa2Gf0n/8Aw4/PLS4QfHWWJ06xVuziMi60jprIpLVVqyAMWPC+m2jRsHq1WHSwo8/wm23hQkK9fR/S0UkYbTOmojUGenLgCxaBIccAkVFYdmPv/0tLAVy9tkwejT8/HPc1YqIrD2FNRGpNT7+GCZPhiuvhE03DV2mXbrAs8+GtduaNoVjjoHHHoOvv467WhGRysmJuwARkeqQGqM2fDh06xZuqcdPPAH5+TByZLi9/HLYUWGffeCoo8Ltj3+M+xuIiJROLWsiUisUFBQHNQg/hw8Px//wB+jRA+69N6zhNmUKXH116Ba99FJo2xZ22gkuuQQmTtQG8yKSLJpgICJ12ty5YWLCyJFheZBff4XNNgsL8h51VAh5G20Ud5UiUtuszQQDhTURkciyZWHNtpEjwwzTpUuhQQPo3h169Qrj3po3j7tKEakNFNZERNbRqlXw9tshuL3yCsyZE47n5oYWt169YNddw9g3EZG1pbAmIlKN3OGTT0JoGzkS3n8/HGvVqji4dekC660Xd6Uiki0U1kREMmjRorBu28iR8MYbYU23TTaBQw8Nwa1nT2jcOO4qRSTJFNZERGrI8uUhsI0cGSYqFBVBTk7YbD61LMjw4WHbq9RMVQhLjRQUwMCB8dUuIvFRWBMRicFvv8EHHxR3l37ySTi+7bahNe7228NuCvn5a64JJyJ1j8KaiEgCfPpp8UK8EyeGcW6NGoXlQW6+GS64AOrXj7tKEYmD9gYVEUmAHXaAAQNCS1pRURjP9sMPIaz17x+WATn33NCN+uuvcVcrIkmlsCYiUgM++ggmTQr7ljZuHH4ecAA89VRYeHfLLeHMM8P6bitWxF2tiCSJwpqISIal71t63XXw/PPwwAOhVa2oCF56CQ47DF58MeycsPnmcPLJ4fHy5XFXLyJxU1gTEcmw8vYtXX99OPpoePJJ+PprGDsWjj8+7KRw3HHQrBmccAI8+2zoQhWRukcTDEREEmjVqjDWbcSI0PK2aFHxhvTHHReWBGnSJO4qRaSqNBtURKQW+e03ePddeOGF0DU6d25Yy6179xDcjj46tMCJSPZQWBMRqaXcQ/fpiBHh9tlnUK9e2O7quOPg2GO12bxINlBYExGpA9zDLNMXXgjBLbUI7z77FAe31q1jLVFEyqCwJiJSB33ySXGL24cfhmN77BGC23HHwY47xlufiBTTorgiInVQ27YweDBMnQqzZ8Ott4YdEi6/HHbaCdq3h2uvhY8/Dq1yAEOGhKVF0uXlheMikgwKayIitdB224VN4t9/H776CoYODYvxXnst7Lor/PGPIcRtsklYAy4V2FJrwnXqFG/9IlJM3aAiInXIokVhKZARI2DChDDTdIstYNkyOPFEGD1aG8yL1AR1g4qISKm23BLOOw/+858Q3B55BHbfPWxxNWwY/PQTPPooPPccfPdd3NWKCCisiYjUWU2bhv1IL7kkLLB73HGwejWMHAl9+oTnu3WD22+HmTOLx7mJSM1SWBMRqcNSY9Sefz4sAfLqq9CgAdx9dxjz9s03cPHFYYzbjjtC//4wfjysXBl35SJ1h8KaiEgdVta+pT//DDfdFNZx++ILuO8+2GGHsAH9QQeFVrcTToDHHw97mopI5mR0goGZ9QTuAuoDD7v7LSWe3wZ4HGgcnXOZu48t8fwM4Bp3v628z9IEAxGRzPvpp9CyNnp0uC1cCGaw555wxBHh1r59OCYiZUvEorhmVh+YBRwMzAMKgL7uPiPtnIeAqe7+gJm1A8a6e+u050cAq4H3FdZERJLFPazplgpuBQXheMuWxcHtwANh/fXjrVMkiZIyG7QzMNvd57j7SuBZoFeJcxzYOLq/CbAg9YSZHQ3MAaZnsEYREakiszCT9Kqr4IMPQivbI4+ENdqefDKEtc02gyOPhH/+E+bNi7tikeyUk8H3bgHMTXs8D9izxDnXAK+b2QXAhsBBAGa2IXApoVXu4gzWKCIi1WTLLcPs0jPPDEuB5OfDmDEwalRoeQPo0KG41a1Tp7AJvYiUL5P/mZQ2YqFkn2tfYJi7twQOA540s3rAtcCd7v5juR9gdraZFZpZYVFRUbUULSIi6+4Pf4AePeCuu+Czz2DGjLCF1cYbh4kLe+0FW20FZ5wRFuhdtizuikWSK5Nj1vYmTAw4JHo8CMDdb047ZzrQ093nRo/nAHsBI4Cto9MaE8atXeXu95b1eRqzJiKSHb79FsaNC61tr74KS5fCeutB167FrW7bbRfCXadOa+6mkJcXxsYNHBhf/SLVISlj1gqAHcysjZk1APoAI0uc8xXQHcDM2gINgSJ339/dW0eTDYYCN5UX1EREJHtsuin07QtPPx2W/XjrrbB+24IFcNFFsP32YVP6yZPhmGPgjTfC67RvqdRVGRuz5u6rzOx8YBxhWY5H3X26mV0HFLr7SGAA8C8z60/oIj3da8tmpSIiUqGcHNh//3C79VaYMyeMcxs9Gl5+OSy+e8gh0K5d2JD+6ae1b6nUPdrIXUREEumHH8IeptdeC9OmhWM5OaG79Kijwq1161hLFKmypHSDioiIVFmjRtC4McyfD1dcEe6fcEJYIuTCC6FNm7AA75VXhnFsq1fHXbFIZiisiYhIIqXGqA0fDjfcAC++GMav3XsvfPop3HFHWMft5puhc+ewGO8554Ru1J9/jrt6keqjsCYiIolU1r6lBQVhEkL//iHQLV4cFuHdbz/497/DbNKmTcPkhMceA63sJNlOY9ZERKTWSC3GO3JkuM2dG3Za2Gef4nFuO+2kvUslfonYG7SmKayJiEg6d/jww+LgNmVKOL7DDsXBbZ99wqQFkZqmsCYiIlLC3LlhSZBXXoE334Rffw1j3g4/PAS3Hj3CpAaRmqCwJiIiUo5ly+D110OL25gxYVeFBg3gwANDcDvyyDBhQSRTFNZEREQqadUqeOed0OL2yithL1OAPfYo7i7dbTeNc5PqpXXWREREKiknB7p0gdtvD0uCzJgBt9wSNqO/5hro2BFatYLzzw+tcStXhn1L8/LWfJ+8vHBcpLoprImIiETMwr6kl14KkybBokXwyCOw++7w6KNh66umTWHsWOjVK7TEgfYtlcxSN6iIiEgl/PwzjB8fxrmNGhWCHMDWW8M338CNN8J554UWOZGKaMyaiIhIBq1eDYWF8Pe/hxa4lIYNw+K83bqFyQq5uVoaREq3NmFNv0IiIiJrqV49+OknmDkz7E16//1w0UWwZEnoEr3iinBeo0ZhPFwqvO22W3ityNpQWBMREVlL6fuWdusWbqnHQ4eGLa4mTAjrueXlheVBADbdFA44oDi8tW2rWaZSMXWDioiIrKUhQ8JkgtS+pRBCWUEBDBz4+/Pnzw/Pv/lmuH35ZTi+xRYhtKXC27bbKrzVFRqzJiIikmCff14c3PLyYOHCcHybbUJoSwU4LcxbeymsiYiIZAn3MPYtFd4mTAizSyHsY5oKbwccAJtvHmelUp0U1kRERLLU6tXw3/8Wh7e33grbYwHssktxeOvaFRo3jrdWqTqFNRERkVpi1SqYMqU4vL39dljzrV69sLtCKrzttx9stNHaj6eTeCisiYiI1FIrVsAHHxSHt/feC1tg5eRA585hksKoUfDcc2HHhZIzVyUZFNZERETqiOXLw0b0qfBWWAi//Raea9UqLCNy/fVhd4X114+3VimmsCYiIlJHLVsGEyeGgPb++8XHGzQILW9du4bb3nuHblOJx9qENa2jLCIiUotsvDFssAF89lnYXWGzzeCmm+DCC0N36S23QI8e0KQJ7LVX2LR+7NjiSQySPGpZExERqUVKjlEr+fiHH+DddyE/P9w++AB+/bV4wkKq5W2//cKOC5IZ6gYVERGpo9Z2Nujy5WGSQiq8vfdemMRgBrvuWhzeunSBZs1q7nvUdgprIiIiUiW//BJa2/Lzwxpv77wTAh1Au3bFwa1rV9hqq3hrzWYKayIiIlItVq6EyZOLW97efht+/DE8t8MOxS1vXbvC1lvHW2s2UVgTERGRjFi1CqZOLQ5vEyfC99+H59q0WbPlrU0bbUxfFoU1ERERqRG//Ra2x0qFt7feKt7btGXLNVveXnopLB+i3RUU1kRERCQmq1fDjBnF4S0/H77+OjzXpEkY/3buuXD22bBoEZx4Yt3cXUFhTURERBLBHWbODC1u+fnw+uuwZEl4ziy0uPXpE/Y33X77utNtqrAmIiIiieQO/frBvffCLruELtOFC8NzLVqEFrZu3UJ4a9061lIzam3CWk6mixERERFJmTABnn027K7wwANhw/kWLcLYtbw8GDcOnnoqnNu6dXF469YtjIGri9SyJiIiIjWiot0VILS8TZ9eHN4mTIClS8NzO+ywZnjbYovYvso6UzeoiIiIJM7a7q4AYcLCtGnF4S0/P2yZBWGR3lRwO+CAsA9qtlBYExERkVpp1SqYMqU4vE2cWLzDwm67FYe3Ll2gceN4ay2PwpqIiIjUCStXhpa5VHibNCnsbZramP7AA0N4228/aNQo7mqLrU1Yq5fhQnqa2Uwzm21ml5Xy/DZmlmdmU83sIzM7LDp+sJlNNrP/Rj8PzGSdIiIikp0aNIB994XBg2H8ePjuuxDaBg+GDTaAoUPhsMPCGm977w1XXAH/+U9xa1zKkCHhdeny8sLxuGWsZc3M6gOzgIOBeUAB0NfdZ6Sd8xAw1d0fMLN2wFh3b21mHYHF7r7AzHYBxrl7i/I+Ty1rIiIiUtLy5aG1LdXyVlAQdl1o0AD23LN4mZBffoFTTil/8kN1SsrSHZ2B2e4+JyrqWaAXMCPtHAc2ju5vAiwAcPepaedMBxqa2R/cfUUG6xUREZFaZoMN4OCDww3C5ISJE4vD2/XXw3XXQcOG0LYtHHFECGmjRydnZ4VMhrUWwNy0x/OAPUuccw3wupldAGwIHFTK+xxHaH1TUBMREZF10qhR6BY97LDweOnSsLtCKrwtXw7DhoV14JIQ1CCzY9ZK2zCiZJ9rX2CYu7cEDgOeNLP/r8nMdgZuBc4p9QPMzjazQjMrLCoqqqayRUREpK5o0gR69Qpj24YODct/nHVWWLC35Bi2uGQyrM0Dtk573JKomzPNn4HhAO7+LtAQaApgZi2Bl4BT3f2z0j7A3R9y91x3z23WrFk1ly8iIiJ1RWqM2vPPw7/+FbpAe/dORmDLZFgrAHYwszZm1gDoA4wscc5XQHcAM2tLCGtFZtYYGAMMcvdJGaxRREREhIKCNceodesWHhcUxFsXZHidtWgpjqFAfeBRd7/RzK4DCt19ZDQD9F/ARoQu0oHu/rqZDQYGAZ+mvV0Pd/+6rM/SbFARERHJFloUV0RERCTBErMoroiIiIisG4U1ERERkQRTWBMRERFJMIU1ERERkQRTWBMRERFJMIU1ERERkQRTWBMRERFJsFqzzpqZFQFfxl1HzJoCS+IuIgvpulWNrlvV6LpVja5b1ei6VU1NXLdW7l6pvTJrTVgTMLPCyi6wJ8V03apG161qdN2qRtetanTdqiZp103doCIiIiIJprAmIiIikmAKa7XLQ3EXkKV03apG161qdN2qRtetanTdqiZR101j1kREREQSTC1rIiIiIgmmsJblzGxrM8szs0/MbLqZXRh3TdnEzOqb2VQzGx13LdnCzBqb2Qtm9r/o927vuGvKBmbWP/pv9GMze8bMGsZdU1KZ2aNm9rWZfZx2bFMze8PMPo1+NomzxiQq47r9I/pv9SMze8nMGsdZYxKVdt3SnrvYzNzMmsZRW4rCWvZbBQxw97bAXsDfzKxdzDVlkwuBT+IuIsvcBbzm7n8EdkPXr0Jm1gLoB+S6+y5AfaBPvFUl2jCgZ4ljlwHj3X0HYHz0WNY0jN9ftzeAXdy9PTALGFTTRWWBYfz+umFmWwMHA1/VdEElKaxlOXdf6O5Tovs/EP5wtoi3quxgZi2Bw4GH464lW5jZxkAX4BEAd1/p7t/FW1XWyAHWN7McYANgQcz1JJa7vwV8W+JwL+Dx6P7jwNE1WlQWKO26ufvr7r4qevge0LLGC0u4Mn7fAO4EBgKxD+5XWKtFzKw10BF4P95KssZQwn+Iq+MuJItsCxQBj0Xdxw+b2YZxF5V07j4fuI3w/9AXAt+7++vxVpV1tnD3hRD+Tyqwecz1ZKMzgVfjLiIbmNlRwHx3nxZ3LaCwVmuY2UbACOAid18Wdz1JZ2ZHAF+7++S4a8kyOcDuwAPu3hH4CXVHVSgaX9ULaAM0BzY0s1PirUrqEjO7gjBs5um4a0k6M9sAuAK4Ku5aUhTWagEzW48Q1J529xfjridL7AscZWZfAM8CB5rZU/GWlBXmAfPcPdV6+wIhvEn5DgI+d/cid/8VeBHYJ+aass1iM9sKIPr5dcz1ZA0zOw04AjjZtV5XZWxH+D9W06K/ES2BKWa2ZVwFKaxlOTMzwvihT9z9jrjryRbuPsjdW7p7a8JA7zfdXS0dFXD3RcBcM9spOtQdmBFjSdniK2AvM9sg+m+2O5qYsbZGAqdF908DXomxlqxhZj2BS4Gj3H153PVkA3f/r7tv7u6to78R84Ddo//9i4XCWvbbF/gToWXow+h2WNxFSa12AfC0mX0EdABuirmexItaIl8ApgD/Jfxvb6JWSE8SM3sGeBfYyczmmdmfgVuAg83sU8IMvVvirDGJyrhu9wKNgDeivw8PxlpkApVx3RJFOxiIiIiIJJha1kREREQSTGFNREREJMEU1kREREQSTGFNREREJMEU1kREREQSTGFNRKQUZtbazD6Ouw4REYU1ERERkQRTWBMRqYCZbRttXN8p7lpEpO5RWBMRKUe0tdYI4Ax3L4i7HhGpe3LiLkBEJMGaEfagPM7dp8ddjIjUTWpZExEp2/fAXMIevCIisVDLmohI2VYCRwPjzOxHd/933AWJSN2jsCYiUg53/8nMjgDeMLOf3P2VuGsSkbrF3D3uGkRERESkDBqzJiIiIpJgCmsiIiIiCaawJiIiIpJgCmsiIiIiCaawJiIiIpJgCmsiIiIiCaawJiIiIpJgCmsiIiIiCfZ/1W7enmpbBoUAAAAASUVORK5CYII=\n",
"text/plain": [
"
"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Size of graph\n",
"plt.rcParams['figure.figsize'] = [10,5]\n",
"\n",
"# k means determine k\n",
"distortions = []\n",
"K = range(1,15)\n",
"for k in K:\n",
" kmeanModel = KMeans(n_clusters=k).fit(features)\n",
" kmeanModel.fit(features)\n",
" distortions.append(sum(np.min(cdist(features, kmeanModel.cluster_centers_, 'euclidean'), axis=1)) / features.shape[0])\n",
"\n",
"# Plot the elbow\n",
"plt.plot(K, distortions, 'bx-')\n",
"plt.xlabel('k')\n",
"plt.ylabel('Distortion')\n",
"plt.title('The Elbow Method showing the optimal k')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It seems as though by using the elbow method, the ideal number for k (number of clusters) is 3 as that is when the distortion decreases drop off. That's relatively small compared to the 15 publications that we actually have, but it may be more useful to work with for prediction.\n",
"\n",
"### K-means\n",
"\n",
"The first clustering method I'll use for modelling the dataset is K-means, that requires the user to input k number of centroids, determining the nearest centroid for each data point, and adjusting the centroids until the best clusters are found, or until a set number of iterations has passed. However, we want to see if we can cluster the articles into 15 clusters representing each of the publishers, so that will be k."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"