1490 lines
81 KiB
Plaintext
1490 lines
81 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Chapter 14: Association Rules and Collaborative Filtering\n",
|
|
"\n",
|
|
"> (c) 2019 Galit Shmueli, Peter C. Bruce, Peter Gedeck \n",
|
|
">\n",
|
|
"> Code included in\n",
|
|
">\n",
|
|
"> _Data Mining for Business Analytics: Concepts, Techniques, and Applications in Python_ (First Edition) \n",
|
|
"> Galit Shmueli, Peter C. Bruce, Peter Gedeck, and Nitin R. Patel. 2019.\n",
|
|
"\n",
|
|
"## Import required packages"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"from pathlib import Path\n",
|
|
"\n",
|
|
"import heapq\n",
|
|
"from collections import defaultdict\n",
|
|
"\n",
|
|
"import pandas as pd\n",
|
|
"import matplotlib.pylab as plt\n",
|
|
"from mlxtend.frequent_patterns import apriori\n",
|
|
"from mlxtend.frequent_patterns import association_rules\n",
|
|
"\n",
|
|
"from surprise import Dataset, Reader, KNNBasic\n",
|
|
"from surprise.model_selection import train_test_split\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Table 14.4"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {
|
|
"scrolled": true,
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>Red</th>\n",
|
|
" <th>White</th>\n",
|
|
" <th>Blue</th>\n",
|
|
" <th>Orange</th>\n",
|
|
" <th>Green</th>\n",
|
|
" <th>Yellow</th>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>Transaction</th>\n",
|
|
" <th></th>\n",
|
|
" <th></th>\n",
|
|
" <th></th>\n",
|
|
" <th></th>\n",
|
|
" <th></th>\n",
|
|
" <th></th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>1</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>2</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>3</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>4</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>5</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>6</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>7</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>8</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>9</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>10</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" Red White Blue Orange Green Yellow\n",
|
|
"Transaction \n",
|
|
"1 1 1 0 0 1 0\n",
|
|
"2 0 1 0 1 0 0\n",
|
|
"3 0 1 1 0 0 0\n",
|
|
"4 1 1 0 1 0 0\n",
|
|
"5 1 0 1 0 0 0\n",
|
|
"6 0 1 1 0 0 0\n",
|
|
"7 1 0 1 0 0 0\n",
|
|
"8 1 1 1 0 1 0\n",
|
|
"9 1 1 1 0 0 0\n",
|
|
"10 0 0 0 0 0 1"
|
|
]
|
|
},
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Load and preprocess data set \n",
|
|
"fp_df = pd.read_csv('Faceplate.csv')\n",
|
|
"fp_df.set_index('Transaction', inplace=True)\n",
|
|
"fp_df"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" antecedents consequents support confidence lift leverage\n",
|
|
"13 (White, Red) (Green) 0.2 0.5 2.500000 0.12\n",
|
|
"15 (Green) (White, Red) 0.2 1.0 2.500000 0.12\n",
|
|
"4 (Green) (Red) 0.2 1.0 1.666667 0.08\n",
|
|
"12 (White, Green) (Red) 0.2 1.0 1.666667 0.08\n",
|
|
"7 (Orange) (White) 0.2 1.0 1.428571 0.06\n",
|
|
"8 (Green) (White) 0.2 1.0 1.428571 0.06\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/noah/.local/lib/python3.10/site-packages/mlxtend/frequent_patterns/fpcommon.py:111: DeprecationWarning: DataFrames with non-bool types result in worse computationalperformance and their support might be discontinued in the future.Please use a DataFrame with bool type\n",
|
|
" warnings.warn(\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# create frequent itemsets\n",
|
|
"itemsets = apriori(fp_df, min_support=0.2, use_colnames=True)\n",
|
|
"\n",
|
|
"# and convert into rules\n",
|
|
"rules = association_rules(itemsets, metric='confidence', min_threshold=0.5)\n",
|
|
"rules.sort_values(by=['lift'], ascending=False).head(6)\n",
|
|
"\n",
|
|
"print(rules.sort_values(by=['lift'], ascending=False)\n",
|
|
" .drop(columns=['antecedent support', 'consequent support', 'conviction'])\n",
|
|
" .head(6))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>antecedents</th>\n",
|
|
" <th>consequents</th>\n",
|
|
" <th>antecedent support</th>\n",
|
|
" <th>consequent support</th>\n",
|
|
" <th>support</th>\n",
|
|
" <th>confidence</th>\n",
|
|
" <th>lift</th>\n",
|
|
" <th>leverage</th>\n",
|
|
" <th>conviction</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>13</th>\n",
|
|
" <td>(White, Red)</td>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>0.4</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.5</td>\n",
|
|
" <td>2.500000</td>\n",
|
|
" <td>0.12</td>\n",
|
|
" <td>1.6</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>4</th>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>(Red)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.6</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.666667</td>\n",
|
|
" <td>0.08</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>12</th>\n",
|
|
" <td>(White, Green)</td>\n",
|
|
" <td>(Red)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.6</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.666667</td>\n",
|
|
" <td>0.08</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>7</th>\n",
|
|
" <td>(Orange)</td>\n",
|
|
" <td>(White)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.7</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.428571</td>\n",
|
|
" <td>0.06</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>8</th>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>(White)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.7</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.428571</td>\n",
|
|
" <td>0.06</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>14</th>\n",
|
|
" <td>(Green, Red)</td>\n",
|
|
" <td>(White)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.7</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.428571</td>\n",
|
|
" <td>0.06</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" antecedents consequents antecedent support consequent support \\\n",
|
|
"13 (White, Red) (Green) 0.4 0.2 \n",
|
|
"4 (Green) (Red) 0.2 0.6 \n",
|
|
"12 (White, Green) (Red) 0.2 0.6 \n",
|
|
"7 (Orange) (White) 0.2 0.7 \n",
|
|
"8 (Green) (White) 0.2 0.7 \n",
|
|
"14 (Green, Red) (White) 0.2 0.7 \n",
|
|
"\n",
|
|
" support confidence lift leverage conviction \n",
|
|
"13 0.2 0.5 2.500000 0.12 1.6 \n",
|
|
"4 0.2 1.0 1.666667 0.08 inf \n",
|
|
"12 0.2 1.0 1.666667 0.08 inf \n",
|
|
"7 0.2 1.0 1.428571 0.06 inf \n",
|
|
"8 0.2 1.0 1.428571 0.06 inf \n",
|
|
"14 0.2 1.0 1.428571 0.06 inf "
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# filter to get rules with single consequents only\n",
|
|
"rules[[len(c) == 1 for c in rules.consequents]].sort_values(by=['lift'], ascending=False).head(6)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The apriori method accepts sparse data frames as well. If we convert the original data frame to sparse format, we can see that the memory requirements go down to 40%. The `fill_value` argument informs the `to_sparse` method here which fields to ignore in each transaction."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/noah/.local/lib/python3.10/site-packages/mlxtend/frequent_patterns/fpcommon.py:111: DeprecationWarning: DataFrames with non-bool types result in worse computationalperformance and their support might be discontinued in the future.Please use a DataFrame with bool type\n",
|
|
" warnings.warn(\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>antecedents</th>\n",
|
|
" <th>consequents</th>\n",
|
|
" <th>antecedent support</th>\n",
|
|
" <th>consequent support</th>\n",
|
|
" <th>support</th>\n",
|
|
" <th>confidence</th>\n",
|
|
" <th>lift</th>\n",
|
|
" <th>leverage</th>\n",
|
|
" <th>conviction</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>13</th>\n",
|
|
" <td>(White, Red)</td>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>0.4</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.5</td>\n",
|
|
" <td>2.500000</td>\n",
|
|
" <td>0.12</td>\n",
|
|
" <td>1.6</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>15</th>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>(White, Red)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.4</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>2.500000</td>\n",
|
|
" <td>0.12</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>4</th>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>(Red)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.6</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.666667</td>\n",
|
|
" <td>0.08</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>12</th>\n",
|
|
" <td>(White, Green)</td>\n",
|
|
" <td>(Red)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.6</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.666667</td>\n",
|
|
" <td>0.08</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>7</th>\n",
|
|
" <td>(Orange)</td>\n",
|
|
" <td>(White)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.7</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.428571</td>\n",
|
|
" <td>0.06</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>8</th>\n",
|
|
" <td>(Green)</td>\n",
|
|
" <td>(White)</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>0.7</td>\n",
|
|
" <td>0.2</td>\n",
|
|
" <td>1.0</td>\n",
|
|
" <td>1.428571</td>\n",
|
|
" <td>0.06</td>\n",
|
|
" <td>inf</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" antecedents consequents antecedent support consequent support \\\n",
|
|
"13 (White, Red) (Green) 0.4 0.2 \n",
|
|
"15 (Green) (White, Red) 0.2 0.4 \n",
|
|
"4 (Green) (Red) 0.2 0.6 \n",
|
|
"12 (White, Green) (Red) 0.2 0.6 \n",
|
|
"7 (Orange) (White) 0.2 0.7 \n",
|
|
"8 (Green) (White) 0.2 0.7 \n",
|
|
"\n",
|
|
" support confidence lift leverage conviction \n",
|
|
"13 0.2 0.5 2.500000 0.12 1.6 \n",
|
|
"15 0.2 1.0 2.500000 0.12 inf \n",
|
|
"4 0.2 1.0 1.666667 0.08 inf \n",
|
|
"12 0.2 1.0 1.666667 0.08 inf \n",
|
|
"7 0.2 1.0 1.428571 0.06 inf \n",
|
|
"8 0.2 1.0 1.428571 0.06 inf "
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Convert data set into a sparse data frame\n",
|
|
"#sparse_df = fp_df.to_sparse(fill_value=0)\n",
|
|
"sparse_df = fp_df.astype(pd.SparseDtype(int, fill_value=0))\n",
|
|
"#print('Density {}'.format(sparse_df.density))\n",
|
|
"\n",
|
|
"# create frequent itemsets\n",
|
|
"itemsets = apriori(sparse_df, min_support=0.2, use_colnames=True)\n",
|
|
"\n",
|
|
"# and convert into rules\n",
|
|
"rules = association_rules(itemsets, metric='confidence', min_threshold=0.5)\n",
|
|
"rules.sort_values(by=['lift'], ascending=False).head(6)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Data required for Table 14.5 and 14.6"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"[{8}, {8, 3, 4}, {8}, {9, 3}, {9}, {8, 1}, {9, 6}, {9, 3, 5, 7}, {8}, set(), {1, 9, 7}, {1, 4, 5, 8, 9}, {9, 5, 7}, {8, 6, 7}, {9, 3, 7}, {1, 4, 9}, {8, 6, 7}, {8}, set(), {9}, {8, 2, 5, 6}, {9, 4, 6}, {9, 4}, {8, 9}, {8, 6}, {8, 1, 6}, {8, 5}, {8, 9, 4}, {9}, {8}, {8, 1, 5}, {9, 3, 6}, {9, 7}, {8, 9, 7}, {8, 3, 4, 6}, {8, 1, 4}, {8, 4, 7}, {8, 9}, {9, 4, 5, 7}, {8, 9, 2}, {9, 2, 5}, {1, 2, 9, 7}, {8, 5}, {8, 1, 7}, {8}, {9, 2, 7}, {9, 4, 6}, {9}, {9}, {8, 6, 7}]\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>1</th>\n",
|
|
" <th>2</th>\n",
|
|
" <th>3</th>\n",
|
|
" <th>4</th>\n",
|
|
" <th>5</th>\n",
|
|
" <th>6</th>\n",
|
|
" <th>7</th>\n",
|
|
" <th>8</th>\n",
|
|
" <th>9</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>0</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>1</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>2</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>3</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>4</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" 1 2 3 4 5 6 7 8 9\n",
|
|
"0 0 0 0 0 0 0 0 1 0\n",
|
|
"1 0 0 1 1 0 0 0 1 0\n",
|
|
"2 0 0 0 0 0 0 0 1 0\n",
|
|
"3 0 0 1 0 0 0 0 0 1\n",
|
|
"4 0 0 0 0 0 0 0 0 1"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Prepare the dataset for table 14.6 based on table 14.5\n",
|
|
"from itertools import chain\n",
|
|
"randomTransactions = [{8}, {3,4,8}, {8}, {3,9}, {9}, {1,8}, {6,9}, {3,5,7,9}, {8}, set(), \n",
|
|
" {1,7,9}, {1,4,5,8,9}, {5,7,9}, {6,7,8}, {3,7,9}, {1,4,9}, {6,7,8}, {8}, set(), {9},\n",
|
|
" {2,5,6,8}, {4,6,9}, {4,9}, {8,9}, {6,8}, {1,6,8}, {5,8}, {4,8,9}, {9}, {8},\n",
|
|
" {1,5,8}, {3,6,9}, {7,9}, {7,8,9}, {3,4,6,8}, {1,4,8}, {4,7,8}, {8,9}, {4,5,7,9}, {2,8,9},\n",
|
|
" {2,5,9}, {1,2,7,9}, {5,8}, {1,7,8}, {8}, {2,7,9}, {4,6,9}, {9}, {9}, {6,7,8}]\n",
|
|
"print(randomTransactions)\n",
|
|
"uniqueItems = sorted(set(chain.from_iterable(randomTransactions)))\n",
|
|
"randomData = pd.DataFrame(0, index=range(len(randomTransactions)), columns=uniqueItems)\n",
|
|
"for row, transaction in enumerate(randomTransactions):\n",
|
|
" for item in transaction:\n",
|
|
" randomData.loc[row][item] = 1\n",
|
|
"randomData.head()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Table 14.6"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" antecedents consequents support confidence lift leverage\n",
|
|
"3 (8, 3) (4) 0.04 1.0 4.545455 0.0312\n",
|
|
"1 (1, 5) (8) 0.04 1.0 1.851852 0.0184\n",
|
|
"2 (2, 7) (9) 0.04 1.0 1.851852 0.0184\n",
|
|
"4 (3, 4) (8) 0.04 1.0 1.851852 0.0184\n",
|
|
"5 (3, 7) (9) 0.04 1.0 1.851852 0.0184\n",
|
|
"6 (4, 5) (9) 0.04 1.0 1.851852 0.0184\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/noah/.local/lib/python3.10/site-packages/mlxtend/frequent_patterns/fpcommon.py:111: DeprecationWarning: DataFrames with non-bool types result in worse computationalperformance and their support might be discontinued in the future.Please use a DataFrame with bool type\n",
|
|
" warnings.warn(\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# create frequent itemsets\n",
|
|
"itemsets = apriori(randomData, min_support=2/len(randomData), use_colnames=True)\n",
|
|
"# and convert into rules\n",
|
|
"rules = association_rules(itemsets, metric='confidence', min_threshold=0.7)\n",
|
|
"print(rules.sort_values(by=['lift'], ascending=False)\n",
|
|
" .drop(columns=['antecedent support', 'consequent support', 'conviction'])\n",
|
|
" .head(6))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Table 14.8"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>ChildBks</th>\n",
|
|
" <th>YouthBks</th>\n",
|
|
" <th>CookBks</th>\n",
|
|
" <th>DoItYBks</th>\n",
|
|
" <th>RefBks</th>\n",
|
|
" <th>ArtBks</th>\n",
|
|
" <th>GeogBks</th>\n",
|
|
" <th>ItalCook</th>\n",
|
|
" <th>ItalAtlas</th>\n",
|
|
" <th>ItalArt</th>\n",
|
|
" <th>Florence</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>0</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>1</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>2</th>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>1</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>3</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>4</th>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" <td>0</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" ChildBks YouthBks CookBks DoItYBks RefBks ArtBks GeogBks ItalCook \\\n",
|
|
"0 0 1 1 0 0 0 0 0 \n",
|
|
"1 0 0 0 0 0 0 0 0 \n",
|
|
"2 1 1 1 0 1 0 1 1 \n",
|
|
"3 0 0 0 0 0 0 0 0 \n",
|
|
"4 0 0 0 0 0 0 0 0 \n",
|
|
"\n",
|
|
" ItalAtlas ItalArt Florence \n",
|
|
"0 0 0 0 \n",
|
|
"1 0 0 0 \n",
|
|
"2 0 0 0 \n",
|
|
"3 0 0 0 \n",
|
|
"4 0 0 0 "
|
|
]
|
|
},
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# load dataset\n",
|
|
"all_books_df = pd.read_csv('CharlesBookClub.csv')\n",
|
|
"\n",
|
|
"# create the binary incidence matrix\n",
|
|
"ignore = ['Seq#', 'ID#', 'Gender', 'M', 'R', 'F', 'FirstPurch', 'Related Purchase',\n",
|
|
" 'Mcode', 'Rcode', 'Fcode', 'Yes_Florence', 'No_Florence']\n",
|
|
"count_books = all_books_df.drop(columns=ignore)\n",
|
|
"count_books[count_books > 0] = 1\n",
|
|
"\n",
|
|
"count_books.head()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"image/png": "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",
|
|
"text/plain": [
|
|
"<Figure size 640x480 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"# determine item frequencies\n",
|
|
"itemFrequency = count_books.sum(axis=0) / len(count_books)\n",
|
|
"\n",
|
|
"# and plot as histogram\n",
|
|
"ax = itemFrequency.plot.bar(color='blue')\n",
|
|
"plt.ylabel('Item frequency (relative)')\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 24,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Number of rules 81\n",
|
|
" antecedents consequents support confidence \\\n",
|
|
"64 (RefBks, YouthBks) (ChildBks, CookBks) 0.05525 0.680000 \n",
|
|
"73 (DoItYBks, RefBks) (ChildBks, CookBks) 0.06125 0.662162 \n",
|
|
"60 (DoItYBks, YouthBks) (ChildBks, CookBks) 0.06700 0.648910 \n",
|
|
"80 (RefBks, GeogBks) (ChildBks, CookBks) 0.05025 0.614679 \n",
|
|
"69 (YouthBks, GeogBks) (ChildBks, CookBks) 0.06325 0.605263 \n",
|
|
"77 (DoItYBks, GeogBks) (ChildBks, CookBks) 0.06050 0.599010 \n",
|
|
"68 (ChildBks, CookBks, GeogBks) (YouthBks) 0.06325 0.577626 \n",
|
|
"72 (RefBks, ChildBks, CookBks) (DoItYBks) 0.06125 0.591787 \n",
|
|
"48 (DoItYBks, GeogBks) (YouthBks) 0.05450 0.539604 \n",
|
|
"63 (RefBks, ChildBks, CookBks) (YouthBks) 0.05525 0.533816 \n",
|
|
"58 (DoItYBks, ChildBks, CookBks) (YouthBks) 0.06700 0.524462 \n",
|
|
"59 (YouthBks, ChildBks, CookBks) (DoItYBks) 0.06700 0.558333 \n",
|
|
"34 (RefBks, ChildBks) (DoItYBks) 0.07100 0.553606 \n",
|
|
"76 (ChildBks, CookBks, GeogBks) (DoItYBks) 0.06050 0.552511 \n",
|
|
"21 (ChildBks, GeogBks) (YouthBks) 0.07550 0.516239 \n",
|
|
"46 (CookBks, GeogBks) (YouthBks) 0.08025 0.513600 \n",
|
|
"61 (RefBks, YouthBks, ChildBks) (CookBks) 0.05525 0.891129 \n",
|
|
"17 (YouthBks, ChildBks) (DoItYBks) 0.08025 0.544068 \n",
|
|
"51 (RefBks, CookBks) (DoItYBks) 0.07450 0.533095 \n",
|
|
"28 (RefBks) (ChildBks, CookBks) 0.10350 0.505495 \n",
|
|
"71 (DoItYBks, RefBks, CookBks) (ChildBks) 0.06125 0.822148 \n",
|
|
"15 (YouthBks) (ChildBks, CookBks) 0.12000 0.503673 \n",
|
|
"70 (DoItYBks, RefBks, ChildBks) (CookBks) 0.06125 0.862676 \n",
|
|
"24 (ChildBks, CookBks) (DoItYBks) 0.12775 0.527893 \n",
|
|
"25 (DoItYBks) (ChildBks, CookBks) 0.12775 0.501472 \n",
|
|
"\n",
|
|
" lift leverage \n",
|
|
"64 2.809917 0.035588 \n",
|
|
"73 2.736207 0.038865 \n",
|
|
"60 2.681448 0.042014 \n",
|
|
"80 2.539995 0.030467 \n",
|
|
"69 2.501087 0.037961 \n",
|
|
"77 2.475248 0.036058 \n",
|
|
"68 2.424452 0.037162 \n",
|
|
"72 2.323013 0.034883 \n",
|
|
"48 2.264864 0.030437 \n",
|
|
"63 2.240573 0.030591 \n",
|
|
"58 2.201309 0.036564 \n",
|
|
"59 2.191691 0.036430 \n",
|
|
"34 2.173135 0.038328 \n",
|
|
"76 2.168838 0.032605 \n",
|
|
"21 2.166797 0.040656 \n",
|
|
"46 2.155719 0.043023 \n",
|
|
"61 2.144715 0.029489 \n",
|
|
"17 2.135693 0.042674 \n",
|
|
"51 2.092619 0.038899 \n",
|
|
"28 2.088820 0.053950 \n",
|
|
"71 2.086669 0.031897 \n",
|
|
"15 2.081292 0.062344 \n",
|
|
"70 2.076236 0.031749 \n",
|
|
"24 2.072198 0.066101 \n",
|
|
"25 2.072198 0.066101 \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/noah/.local/lib/python3.10/site-packages/mlxtend/frequent_patterns/fpcommon.py:111: DeprecationWarning: DataFrames with non-bool types result in worse computationalperformance and their support might be discontinued in the future.Please use a DataFrame with bool type\n",
|
|
" warnings.warn(\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# create frequent itemsets and rules\n",
|
|
"pd.options.display.max_rows = 100\n",
|
|
"itemsets = apriori(count_books, min_support=200/4000, use_colnames=True)\n",
|
|
"rules = association_rules(itemsets, metric='confidence', min_threshold=0.5)\n",
|
|
"\n",
|
|
"print('Number of rules', len(rules))\n",
|
|
"\n",
|
|
"# Display 25 rules with highest lift\n",
|
|
"rules.sort_values(by=['lift'], ascending=False).head(25)\n",
|
|
"\n",
|
|
"print(rules.sort_values(by=['lift'], ascending=False).drop(columns=['antecedent support', 'consequent support', 'conviction']).head(25))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 25,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div>\n",
|
|
"<style scoped>\n",
|
|
" .dataframe tbody tr th:only-of-type {\n",
|
|
" vertical-align: middle;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe tbody tr th {\n",
|
|
" vertical-align: top;\n",
|
|
" }\n",
|
|
"\n",
|
|
" .dataframe thead th {\n",
|
|
" text-align: right;\n",
|
|
" }\n",
|
|
"</style>\n",
|
|
"<table border=\"1\" class=\"dataframe\">\n",
|
|
" <thead>\n",
|
|
" <tr style=\"text-align: right;\">\n",
|
|
" <th></th>\n",
|
|
" <th>antecedents</th>\n",
|
|
" <th>consequents</th>\n",
|
|
" <th>antecedent support</th>\n",
|
|
" <th>consequent support</th>\n",
|
|
" <th>support</th>\n",
|
|
" <th>confidence</th>\n",
|
|
" <th>lift</th>\n",
|
|
" <th>leverage</th>\n",
|
|
" <th>conviction</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" <tr>\n",
|
|
" <th>48</th>\n",
|
|
" <td>(DoItYBks, GeogBks)</td>\n",
|
|
" <td>(YouthBks)</td>\n",
|
|
" <td>0.10100</td>\n",
|
|
" <td>0.23825</td>\n",
|
|
" <td>0.05450</td>\n",
|
|
" <td>0.539604</td>\n",
|
|
" <td>2.264864</td>\n",
|
|
" <td>0.030437</td>\n",
|
|
" <td>1.654554</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>34</th>\n",
|
|
" <td>(RefBks, ChildBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.12825</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.07100</td>\n",
|
|
" <td>0.553606</td>\n",
|
|
" <td>2.173135</td>\n",
|
|
" <td>0.038328</td>\n",
|
|
" <td>1.669490</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>21</th>\n",
|
|
" <td>(ChildBks, GeogBks)</td>\n",
|
|
" <td>(YouthBks)</td>\n",
|
|
" <td>0.14625</td>\n",
|
|
" <td>0.23825</td>\n",
|
|
" <td>0.07550</td>\n",
|
|
" <td>0.516239</td>\n",
|
|
" <td>2.166797</td>\n",
|
|
" <td>0.040656</td>\n",
|
|
" <td>1.574642</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>46</th>\n",
|
|
" <td>(CookBks, GeogBks)</td>\n",
|
|
" <td>(YouthBks)</td>\n",
|
|
" <td>0.15625</td>\n",
|
|
" <td>0.23825</td>\n",
|
|
" <td>0.08025</td>\n",
|
|
" <td>0.513600</td>\n",
|
|
" <td>2.155719</td>\n",
|
|
" <td>0.043023</td>\n",
|
|
" <td>1.566098</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>17</th>\n",
|
|
" <td>(YouthBks, ChildBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.14750</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.08025</td>\n",
|
|
" <td>0.544068</td>\n",
|
|
" <td>2.135693</td>\n",
|
|
" <td>0.042674</td>\n",
|
|
" <td>1.634563</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>51</th>\n",
|
|
" <td>(RefBks, CookBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.13975</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.07450</td>\n",
|
|
" <td>0.533095</td>\n",
|
|
" <td>2.092619</td>\n",
|
|
" <td>0.038899</td>\n",
|
|
" <td>1.596148</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>24</th>\n",
|
|
" <td>(ChildBks, CookBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.24200</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.12775</td>\n",
|
|
" <td>0.527893</td>\n",
|
|
" <td>2.072198</td>\n",
|
|
" <td>0.066101</td>\n",
|
|
" <td>1.578560</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>49</th>\n",
|
|
" <td>(YouthBks, GeogBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.10450</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.05450</td>\n",
|
|
" <td>0.521531</td>\n",
|
|
" <td>2.047227</td>\n",
|
|
" <td>0.027879</td>\n",
|
|
" <td>1.557573</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>42</th>\n",
|
|
" <td>(YouthBks, CookBks)</td>\n",
|
|
" <td>(DoItYBks)</td>\n",
|
|
" <td>0.16100</td>\n",
|
|
" <td>0.25475</td>\n",
|
|
" <td>0.08375</td>\n",
|
|
" <td>0.520186</td>\n",
|
|
" <td>2.041948</td>\n",
|
|
" <td>0.042735</td>\n",
|
|
" <td>1.553207</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <th>43</th>\n",
|
|
" <td>(RefBks, YouthBks)</td>\n",
|
|
" <td>(CookBks)</td>\n",
|
|
" <td>0.08125</td>\n",
|
|
" <td>0.41550</td>\n",
|
|
" <td>0.06825</td>\n",
|
|
" <td>0.840000</td>\n",
|
|
" <td>2.021661</td>\n",
|
|
" <td>0.034491</td>\n",
|
|
" <td>3.653125</td>\n",
|
|
" </tr>\n",
|
|
" </tbody>\n",
|
|
"</table>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
" antecedents consequents antecedent support consequent support \\\n",
|
|
"48 (DoItYBks, GeogBks) (YouthBks) 0.10100 0.23825 \n",
|
|
"34 (RefBks, ChildBks) (DoItYBks) 0.12825 0.25475 \n",
|
|
"21 (ChildBks, GeogBks) (YouthBks) 0.14625 0.23825 \n",
|
|
"46 (CookBks, GeogBks) (YouthBks) 0.15625 0.23825 \n",
|
|
"17 (YouthBks, ChildBks) (DoItYBks) 0.14750 0.25475 \n",
|
|
"51 (RefBks, CookBks) (DoItYBks) 0.13975 0.25475 \n",
|
|
"24 (ChildBks, CookBks) (DoItYBks) 0.24200 0.25475 \n",
|
|
"49 (YouthBks, GeogBks) (DoItYBks) 0.10450 0.25475 \n",
|
|
"42 (YouthBks, CookBks) (DoItYBks) 0.16100 0.25475 \n",
|
|
"43 (RefBks, YouthBks) (CookBks) 0.08125 0.41550 \n",
|
|
"\n",
|
|
" support confidence lift leverage conviction \n",
|
|
"48 0.05450 0.539604 2.264864 0.030437 1.654554 \n",
|
|
"34 0.07100 0.553606 2.173135 0.038328 1.669490 \n",
|
|
"21 0.07550 0.516239 2.166797 0.040656 1.574642 \n",
|
|
"46 0.08025 0.513600 2.155719 0.043023 1.566098 \n",
|
|
"17 0.08025 0.544068 2.135693 0.042674 1.634563 \n",
|
|
"51 0.07450 0.533095 2.092619 0.038899 1.596148 \n",
|
|
"24 0.12775 0.527893 2.072198 0.066101 1.578560 \n",
|
|
"49 0.05450 0.521531 2.047227 0.027879 1.557573 \n",
|
|
"42 0.08375 0.520186 2.041948 0.042735 1.553207 \n",
|
|
"43 0.06825 0.840000 2.021661 0.034491 3.653125 "
|
|
]
|
|
},
|
|
"execution_count": 25,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Filter rules by number of antecedents (maximum 2) and consequents (maximum 1)\n",
|
|
"rules = rules[[len(c) <= 2 for c in rules.antecedents]]\n",
|
|
"rules = rules[[len(c) == 1 for c in rules.consequents]]\n",
|
|
"\n",
|
|
"rules.sort_values(by=['lift'], ascending=False).head(10)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Section 14.2 Collaborative Filtering"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Computing the cosine similarity matrix...\n",
|
|
"Done computing similarity matrix.\n",
|
|
"user: 823519 item: 30 r_ui = 4.00 est = 3.54 {'was_impossible': True, 'reason': 'User and/or item is unknown.'}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"ratings = pd.DataFrame([\n",
|
|
" [30878, 1, 4], [30878, 5, 1], [30878, 18, 3], [30878, 28, 3], [30878, 30, 4], [30878, 44, 5], \n",
|
|
" [124105, 1, 4], \n",
|
|
" [822109, 1, 5], \n",
|
|
" [823519, 1, 3], [823519, 8, 1], [823519, 17, 4], [823519, 28, 4], [823519, 30, 5], \n",
|
|
" [885013, 1, 4], [885013, 5, 5], \n",
|
|
" [893988, 1, 3], [893988, 30, 4], [893988, 44, 4], \n",
|
|
" [1248029, 1, 3], [1248029, 28, 2], [1248029, 30, 4], [1248029, 48, 3], \n",
|
|
" [1503895, 1, 4], \n",
|
|
" [1842128, 1, 4], [1842128, 30, 3], \n",
|
|
" [2238063, 1, 3], \n",
|
|
"], columns=['customerID', 'movieID', 'rating'])\n",
|
|
"\n",
|
|
"reader = Reader(rating_scale=(1, 5))\n",
|
|
"data = Dataset.load_from_df(ratings[['customerID', 'movieID', 'rating']], reader)\n",
|
|
"trainset = data.build_full_trainset()\n",
|
|
"sim_options = {'name': 'cosine', 'user_based': False} # compute cosine similarities between items\n",
|
|
"algo = KNNBasic(sim_options=sim_options)\n",
|
|
"algo.fit(trainset)\n",
|
|
"pred = algo.predict(str(823519), str(30), r_ui=4, verbose=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Table 14.11"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Computing the cosine similarity matrix...\n",
|
|
"Done computing similarity matrix.\n",
|
|
"\n",
|
|
"Top-3 recommended items for each user\n",
|
|
"User 6\n",
|
|
" Item 6 (5.00) Item 77 (2.50) Item 60 (1.00)\n",
|
|
"User 222\n",
|
|
" Item 77 (3.50) Item 75 (2.78)\n",
|
|
"User 424\n",
|
|
" Item 14 (3.50) Item 45 (3.10) Item 54 (2.34)\n",
|
|
"User 87\n",
|
|
" Item 27 (3.00) Item 54 (3.00) Item 82 (3.00) Item 32 (1.00)\n",
|
|
"User 121\n",
|
|
" Item 98 (3.48) Item 32 (2.83)\n",
|
|
"\n",
|
|
"Computing the cosine similarity matrix...\n",
|
|
"Done computing similarity matrix.\n",
|
|
"\n",
|
|
"Top-3 recommended items for each user\n",
|
|
"User 6\n",
|
|
" Item 77 (3.00) Item 60 (3.00) Item 6 (3.00)\n",
|
|
"User 222\n",
|
|
" Item 77 (2.24) Item 75 (2.00)\n",
|
|
"User 424\n",
|
|
" Item 54 (3.47) Item 14 (3.44) Item 45 (3.00)\n",
|
|
"User 87\n",
|
|
" Item 27 (3.00) Item 32 (3.00) Item 82 (3.00) Item 54 (2.50)\n",
|
|
"User 121\n",
|
|
" Item 32 (3.06) Item 98 (2.31)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import random\n",
|
|
"\n",
|
|
"random.seed(0)\n",
|
|
"nratings = 5000\n",
|
|
"randomData = pd.DataFrame({\n",
|
|
" 'itemID': [random.randint(0,99) for _ in range(nratings)],\n",
|
|
" 'userID': [random.randint(0,999) for _ in range(nratings)],\n",
|
|
" 'rating': [random.randint(1,5) for _ in range(nratings)],\n",
|
|
"})\n",
|
|
"\n",
|
|
"def get_top_n(predictions, n=10):\n",
|
|
" # First map the predictions to each user.\n",
|
|
" byUser = defaultdict(list)\n",
|
|
" for p in predictions:\n",
|
|
" byUser[p.uid].append(p)\n",
|
|
" \n",
|
|
" # For each user, reduce predictions to top-n\n",
|
|
" for uid, userPredictions in byUser.items():\n",
|
|
" byUser[uid] = heapq.nlargest(n, userPredictions, key=lambda p: p.est)\n",
|
|
" return byUser\n",
|
|
"\n",
|
|
"# Convert thes data set into the format required by the surprise package\n",
|
|
"# The columns must correspond to user id, item id and ratings (in that order)\n",
|
|
"reader = Reader(rating_scale=(1, 5))\n",
|
|
"data = Dataset.load_from_df(randomData[['userID', 'itemID', 'rating']], reader)\n",
|
|
"\n",
|
|
"# Split into training and test set\n",
|
|
"trainset, testset = train_test_split(data, test_size=.25, random_state=1)\n",
|
|
"\n",
|
|
"## User-based filtering\n",
|
|
"# compute cosine similarity between users \n",
|
|
"sim_options = {'name': 'cosine', 'user_based': True}\n",
|
|
"algo = KNNBasic(sim_options=sim_options)\n",
|
|
"algo.fit(trainset)\n",
|
|
"\n",
|
|
"# Than predict ratings for all pairs (u, i) that are NOT in the training set.\n",
|
|
"predictions = algo.test(testset)\n",
|
|
"\n",
|
|
"top_n = get_top_n(predictions, n=4)\n",
|
|
"\n",
|
|
"# Print the recommended items for each user\n",
|
|
"print()\n",
|
|
"print('Top-3 recommended items for each user')\n",
|
|
"for uid, user_ratings in list(top_n.items())[:5]:\n",
|
|
" print('User {}'.format(uid))\n",
|
|
" for prediction in user_ratings:\n",
|
|
" print(' Item {0.iid} ({0.est:.2f})'.format(prediction), end='')\n",
|
|
" print()\n",
|
|
"print()\n",
|
|
"\n",
|
|
" \n",
|
|
"## Item-based filtering\n",
|
|
"# compute cosine similarity between users \n",
|
|
"sim_options = {'name': 'cosine', 'user_based': False}\n",
|
|
"algo = KNNBasic(sim_options=sim_options)\n",
|
|
"algo.fit(trainset)\n",
|
|
"\n",
|
|
"# Than predict ratings for all pairs (u, i) that are NOT in the training set.\n",
|
|
"predictions = algo.test(testset)\n",
|
|
"top_n = get_top_n(predictions, n=4)\n",
|
|
"\n",
|
|
"# Print the recommended items for each user\n",
|
|
"print()\n",
|
|
"print('Top-3 recommended items for each user')\n",
|
|
"for uid, user_ratings in list(top_n.items())[:5]:\n",
|
|
" print('User {}'.format(uid))\n",
|
|
" for prediction in user_ratings:\n",
|
|
" print(' Item {0.iid} ({0.est:.2f})'.format(prediction), end='')\n",
|
|
" print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "python"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Computing the cosine similarity matrix...\n",
|
|
"Done computing similarity matrix.\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Prediction(uid=383, iid=7, r_ui=None, est=2.3661840936304324, details={'actual_k': 4, 'was_impossible': False})"
|
|
]
|
|
},
|
|
"execution_count": 28,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"## Build a model using the full dataset\n",
|
|
"trainset = data.build_full_trainset()\n",
|
|
"sim_options = {'name': 'cosine', 'user_based': False}\n",
|
|
"algo = KNNBasic(sim_options=sim_options)\n",
|
|
"algo.fit(trainset)\n",
|
|
"\n",
|
|
"# Predict rating for user 383 and item 7\n",
|
|
"algo.predict(383, 7)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|