openretailscience 0.45.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openretailscience/__init__.py +3 -0
- openretailscience/analysis/__init__.py +0 -0
- openretailscience/analysis/cohort.py +185 -0
- openretailscience/analysis/composite_rank.py +372 -0
- openretailscience/analysis/cross_shop.py +354 -0
- openretailscience/analysis/customer.py +459 -0
- openretailscience/analysis/customer_decision_hierarchy.py +359 -0
- openretailscience/analysis/gain_loss.py +340 -0
- openretailscience/analysis/haversine.py +50 -0
- openretailscience/analysis/product_association.py +418 -0
- openretailscience/analysis/revenue_tree.py +553 -0
- openretailscience/assets/fonts/Poppins-Bold.ttf +0 -0
- openretailscience/assets/fonts/Poppins-LightItalic.ttf +0 -0
- openretailscience/assets/fonts/Poppins-Medium.ttf +0 -0
- openretailscience/assets/fonts/Poppins-Regular.ttf +0 -0
- openretailscience/assets/fonts/Poppins-SemiBold.ttf +0 -0
- openretailscience/constants.py +295 -0
- openretailscience/metrics/__init__.py +0 -0
- openretailscience/metrics/base.py +29 -0
- openretailscience/metrics/distribution/__init__.py +0 -0
- openretailscience/metrics/distribution/acv.py +81 -0
- openretailscience/metrics/distribution/pct_of_stores.py +118 -0
- openretailscience/options.py +842 -0
- openretailscience/plots/__init__.py +0 -0
- openretailscience/plots/area.py +114 -0
- openretailscience/plots/bar.py +375 -0
- openretailscience/plots/broken_timeline.py +197 -0
- openretailscience/plots/cohort.py +94 -0
- openretailscience/plots/heatmap.py +134 -0
- openretailscience/plots/histogram.py +276 -0
- openretailscience/plots/index.py +505 -0
- openretailscience/plots/line.py +379 -0
- openretailscience/plots/period_on_period.py +144 -0
- openretailscience/plots/price.py +275 -0
- openretailscience/plots/scatter.py +382 -0
- openretailscience/plots/styles/__init__.py +1 -0
- openretailscience/plots/styles/colors.py +215 -0
- openretailscience/plots/styles/font_utils.py +62 -0
- openretailscience/plots/styles/graph_utils.py +834 -0
- openretailscience/plots/styles/styling_helpers.py +155 -0
- openretailscience/plots/time.py +160 -0
- openretailscience/plots/tree_diagram.py +813 -0
- openretailscience/plots/venn.py +127 -0
- openretailscience/plots/waterfall.py +217 -0
- openretailscience/plugin.py +238 -0
- openretailscience/segmentation/__init__.py +0 -0
- openretailscience/segmentation/hml.py +97 -0
- openretailscience/segmentation/nlr.py +218 -0
- openretailscience/segmentation/rfm.py +307 -0
- openretailscience/segmentation/segstats.py +1177 -0
- openretailscience/segmentation/threshold.py +165 -0
- openretailscience/utils/__init__.py +0 -0
- openretailscience/utils/date.py +226 -0
- openretailscience/utils/filter_and_label.py +52 -0
- openretailscience/utils/label.py +79 -0
- openretailscience/utils/validation.py +45 -0
- openretailscience-0.45.0.dist-info/METADATA +364 -0
- openretailscience-0.45.0.dist-info/RECORD +60 -0
- openretailscience-0.45.0.dist-info/WHEEL +4 -0
- openretailscience-0.45.0.dist-info/licenses/LICENSE +93 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Market Basket Analysis and Product Association Rules for Retail Optimization.
|
|
2
|
+
|
|
3
|
+
## Business Context
|
|
4
|
+
|
|
5
|
+
Product association analysis (market basket analysis) uncovers the hidden relationships
|
|
6
|
+
in customer purchasing behavior. This intelligence transforms how retailers approach
|
|
7
|
+
merchandising, marketing, and operations by revealing which products naturally sell
|
|
8
|
+
together and why.
|
|
9
|
+
|
|
10
|
+
## The Business Problem
|
|
11
|
+
|
|
12
|
+
Retailers lose revenue from missed cross-selling opportunities and poor product
|
|
13
|
+
placement. Without understanding product associations, stores might:
|
|
14
|
+
- Place complementary items in different aisles, reducing impulse purchases
|
|
15
|
+
- Miss bundling opportunities that could increase average transaction value
|
|
16
|
+
- Stock-out on associated items when promoting a product
|
|
17
|
+
- Fail to identify new product opportunities based on basket gaps
|
|
18
|
+
|
|
19
|
+
## Real-World Applications
|
|
20
|
+
|
|
21
|
+
1. **Strategic Merchandising**
|
|
22
|
+
- Place chips near beer when data shows strong association
|
|
23
|
+
- Position phone cases near phones based on attachment rates
|
|
24
|
+
- Create end-cap displays with products that sell together
|
|
25
|
+
- Optimize shelf space allocation using association strength
|
|
26
|
+
|
|
27
|
+
2. **Dynamic Bundle Pricing**
|
|
28
|
+
- Create "Breakfast bundle": Coffee + Pastry when uplift shows synergy
|
|
29
|
+
- Design seasonal bundles based on historical associations
|
|
30
|
+
- Price bundles to incentivize larger baskets while maintaining margins
|
|
31
|
+
- Test bundle combinations using confidence metrics
|
|
32
|
+
|
|
33
|
+
3. **Personalized Recommendations**
|
|
34
|
+
- Power "Customers who bought X also bought Y" suggestions
|
|
35
|
+
- Enhance cart abandonment recovery with associated items
|
|
36
|
+
- Design email campaigns based on previous purchase associations
|
|
37
|
+
- Improve search results by showing associated products
|
|
38
|
+
|
|
39
|
+
4. **Inventory Optimization**
|
|
40
|
+
- Stock pasta sauce when pasta is promoted (if association exists)
|
|
41
|
+
- Prepare battery inventory when toys are featured
|
|
42
|
+
- Coordinate supply chain for products that sell together
|
|
43
|
+
- Reduce stockouts by understanding product relationships
|
|
44
|
+
|
|
45
|
+
5. **New Product Placement**
|
|
46
|
+
- Place new organic items near existing organic purchases
|
|
47
|
+
- Position private label next to associated national brands
|
|
48
|
+
- Test new products within high-association categories
|
|
49
|
+
- Leverage existing associations to drive trial
|
|
50
|
+
|
|
51
|
+
## Key Metrics Explained
|
|
52
|
+
|
|
53
|
+
### Support (Frequency)
|
|
54
|
+
The proportion of all transactions containing both products. Higher support
|
|
55
|
+
indicates a more common pairing. Use this to identify:
|
|
56
|
+
- Core product relationships for everyday decisions
|
|
57
|
+
- Sufficient sample size for confident conclusions
|
|
58
|
+
- Opportunities worth marketing investment
|
|
59
|
+
|
|
60
|
+
### Confidence (Conditional Probability)
|
|
61
|
+
The probability of buying product B given product A was purchased. This answers
|
|
62
|
+
"If a customer buys A, how likely are they to buy B?" Use this for:
|
|
63
|
+
- Recommendation engine rules
|
|
64
|
+
- Promotional targeting decisions
|
|
65
|
+
- Cross-sell prioritization
|
|
66
|
+
|
|
67
|
+
### Uplift/Lift (Synergy Indicator)
|
|
68
|
+
Measures how much more likely products are bought together than would be expected
|
|
69
|
+
by chance. Uplift = Observed probability / Expected probability. Interpretation:
|
|
70
|
+
- Uplift > 1: Products have positive association (sell better together)
|
|
71
|
+
- Uplift = 1: Products are independent (no relationship)
|
|
72
|
+
- Uplift < 1: Products have negative association (rarely bought together)
|
|
73
|
+
|
|
74
|
+
Higher uplift values indicate stronger synergies worth exploiting.
|
|
75
|
+
|
|
76
|
+
## Actionable Decision Framework
|
|
77
|
+
|
|
78
|
+
Retailers should consider multiple metrics together:
|
|
79
|
+
|
|
80
|
+
**High Support + High Confidence**
|
|
81
|
+
- Strong, frequent relationship
|
|
82
|
+
- Priority for permanent merchandising changes
|
|
83
|
+
- Core bundle candidates
|
|
84
|
+
|
|
85
|
+
**Low Support + High Confidence**
|
|
86
|
+
- Niche but reliable relationship
|
|
87
|
+
- Targeted marketing opportunities
|
|
88
|
+
- Specialized customer segments
|
|
89
|
+
|
|
90
|
+
**High Support + Low Confidence**
|
|
91
|
+
- Common but weak relationship
|
|
92
|
+
- Test before major changes
|
|
93
|
+
- Monitor for shifts over time
|
|
94
|
+
|
|
95
|
+
**High Uplift (regardless of support)**
|
|
96
|
+
- Strong synergy exists
|
|
97
|
+
- Test for merchandising opportunities
|
|
98
|
+
- Consider for promotional strategies
|
|
99
|
+
|
|
100
|
+
## Implementation Considerations
|
|
101
|
+
|
|
102
|
+
- Validate associations across time periods before major changes
|
|
103
|
+
- Consider seasonality in association patterns
|
|
104
|
+
- Test recommendations with A/B experiments
|
|
105
|
+
- Monitor associations as product mix evolves
|
|
106
|
+
- Account for external factors (promotions, events) affecting associations
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
import ibis
|
|
110
|
+
import pandas as pd
|
|
111
|
+
from ibis import _
|
|
112
|
+
|
|
113
|
+
from openretailscience.options import get_option
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ProductAssociation:
|
|
117
|
+
"""A class for generating and analyzing product association rules.
|
|
118
|
+
|
|
119
|
+
This class calculates association rules between products based on transaction data,
|
|
120
|
+
helping to identify patterns in customer purchasing behavior.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
df (pandas.DataFrame): The input DataFrame containing transaction data.
|
|
124
|
+
value_col (str): The name of the column in the input DataFrame that contains
|
|
125
|
+
the product identifiers.
|
|
126
|
+
group_col (str, optional): The name of the column that identifies unique
|
|
127
|
+
transactions or customers. Defaults to option column.column_id.
|
|
128
|
+
target_item (str | float | list[str | float] | None, optional): A specific
|
|
129
|
+
product or list of products to focus the association analysis on.
|
|
130
|
+
If None, associations for all products are calculated. Defaults to None.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
df (pandas.DataFrame): A DataFrame containing the calculated association
|
|
134
|
+
rules and their metrics.
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> import pandas as pd
|
|
138
|
+
>>> transaction_df = pd.DataFrame({
|
|
139
|
+
... 'customer_id': [1, 1, 2, 2, 3],
|
|
140
|
+
... 'product_id': ['A', 'B', 'B', 'C', 'A']
|
|
141
|
+
... })
|
|
142
|
+
>>> pa = ProductAssociation(df=transaction_df, value_col='product_id', group_col='customer_id')
|
|
143
|
+
>>> print(pa.df) # View the calculated association rules
|
|
144
|
+
|
|
145
|
+
Note:
|
|
146
|
+
The resulting DataFrame (pa.df) contains the following columns:
|
|
147
|
+
- product_1, product_2: The pair of products for which the association is calculated.
|
|
148
|
+
- occurrences_1, occurrences_2: The number of transactions containing each product.
|
|
149
|
+
- cooccurrences: The number of transactions containing both products.
|
|
150
|
+
- support: The proportion of transactions containing both products.
|
|
151
|
+
- confidence: The probability of buying product_2 given that product_1 was bought.
|
|
152
|
+
- uplift: The ratio of the observed support to the expected support if the products were independent.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
df: pd.DataFrame | ibis.Table,
|
|
158
|
+
value_col: str,
|
|
159
|
+
group_col: str | None = None,
|
|
160
|
+
target_item: str | None = None,
|
|
161
|
+
min_occurrences: int = 1,
|
|
162
|
+
min_cooccurrences: int = 1,
|
|
163
|
+
min_support: float = 0.0,
|
|
164
|
+
min_confidence: float = 0.0,
|
|
165
|
+
min_uplift: float = 0.0,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Initialize the ProductAssociation object.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
df (pd.DataFrame | ibis.Table) : The input DataFrame or ibis Table containing transaction data.
|
|
171
|
+
value_col (str): The name of the column in the input DataFrame that contains the product identifiers.
|
|
172
|
+
group_col (str, optional): The name of the column that identifies unique transactions or customers. Defaults
|
|
173
|
+
to option column.unit_spend.
|
|
174
|
+
target_item (str | float | list[str | float] | None, optional): A specific product
|
|
175
|
+
or list of products to focus the association analysis on. If None,
|
|
176
|
+
associations for all products are calculated. Defaults to None.
|
|
177
|
+
min_occurrences (int, optional): The minimum number of occurrences required for each product in the
|
|
178
|
+
association analysis. Defaults to 1. Must be at least 1.
|
|
179
|
+
min_cooccurrences (int, optional): The minimum number of co-occurrences required for the product pairs in
|
|
180
|
+
the association analysis. Defaults to 1. Must be at least 1.
|
|
181
|
+
min_support (float, optional): The minimum support value required for the association rules. Defaults to
|
|
182
|
+
0.0. Must be between 0 and 1.
|
|
183
|
+
min_confidence (float, optional): The minimum confidence value required for the association rules. Defaults
|
|
184
|
+
to 0.0. Must be between 0 and 1.
|
|
185
|
+
min_uplift (float, optional): The minimum uplift value required for the association rules. Defaults to 0.0.
|
|
186
|
+
Must be greater or equal to 0.
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
ValueError: If the number of combinations is not 2 or 3, or if any of the minimum values are invalid.
|
|
190
|
+
ValueError: If the minimum support, confidence, or uplift values are outside the valid range.
|
|
191
|
+
ValueError: If the minimum occurrences or cooccurrences are less than 1.
|
|
192
|
+
ValueError: If the input DataFrame does not contain the required columns or if they have null values.
|
|
193
|
+
"""
|
|
194
|
+
self._df: pd.DataFrame | None = None
|
|
195
|
+
group_col = group_col or get_option("column.customer_id")
|
|
196
|
+
required_cols = [group_col, value_col]
|
|
197
|
+
missing_cols = set(required_cols) - set(df.columns)
|
|
198
|
+
if len(missing_cols) > 0:
|
|
199
|
+
msg = f"The following columns are required but missing: {missing_cols}"
|
|
200
|
+
raise ValueError(msg)
|
|
201
|
+
|
|
202
|
+
self.table = self._calc_association(
|
|
203
|
+
df=df,
|
|
204
|
+
value_col=value_col,
|
|
205
|
+
group_col=group_col,
|
|
206
|
+
target_item=target_item,
|
|
207
|
+
min_occurrences=min_occurrences,
|
|
208
|
+
min_cooccurrences=min_cooccurrences,
|
|
209
|
+
min_support=min_support,
|
|
210
|
+
min_confidence=min_confidence,
|
|
211
|
+
min_uplift=min_uplift,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _validate_minimum_values(
|
|
216
|
+
min_occurrences: int,
|
|
217
|
+
min_cooccurrences: int,
|
|
218
|
+
min_support: float,
|
|
219
|
+
min_confidence: float,
|
|
220
|
+
min_uplift: float,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Validate minimum value parameters.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
min_occurrences (int): The minimum number of occurrences required for each product.
|
|
226
|
+
min_cooccurrences (int): The minimum number of co-occurrences required for product pairs.
|
|
227
|
+
min_support (float): The minimum support value required for association rules.
|
|
228
|
+
min_confidence (float): The minimum confidence value required for association rules.
|
|
229
|
+
min_uplift (float): The minimum uplift value required for association rules.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
ValueError: If any parameter is outside the valid range.
|
|
233
|
+
"""
|
|
234
|
+
if min_occurrences < 1:
|
|
235
|
+
raise ValueError("Minimum occurrences must be at least 1")
|
|
236
|
+
if min_cooccurrences < 1:
|
|
237
|
+
raise ValueError("Minimum cooccurrences must be at least 1")
|
|
238
|
+
if min_support < 0.0 or min_support > 1.0:
|
|
239
|
+
raise ValueError("Minimum support must be between 0 and 1")
|
|
240
|
+
if min_confidence < 0.0 or min_confidence > 1.0:
|
|
241
|
+
raise ValueError("Minimum confidence must be between 0 and 1")
|
|
242
|
+
if min_uplift < 0.0:
|
|
243
|
+
raise ValueError("Minimum uplift must be greater or equal to 0")
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _calc_association(
|
|
247
|
+
df: pd.DataFrame | ibis.Table,
|
|
248
|
+
value_col: str,
|
|
249
|
+
group_col: str = get_option("column.customer_id"),
|
|
250
|
+
target_item: str | float | list[str | float] | None = None,
|
|
251
|
+
min_occurrences: int = 1,
|
|
252
|
+
min_cooccurrences: int = 1,
|
|
253
|
+
min_support: float = 0.0,
|
|
254
|
+
min_confidence: float = 0.0,
|
|
255
|
+
min_uplift: float = 0.0,
|
|
256
|
+
) -> pd.DataFrame:
|
|
257
|
+
"""Calculate product association rules based on transaction data.
|
|
258
|
+
|
|
259
|
+
This method calculates association rules between products based on transaction data,
|
|
260
|
+
helping to identify patterns in customer purchasing behavior.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
df (pd.DataFrame | ibis.Table) : The input DataFrame or ibis Table containing transaction data.
|
|
264
|
+
value_col (str): The name of the column in the input DataFrame that contains the product identifiers.
|
|
265
|
+
group_col (str, optional): The name of the column that identifies unique transactions or customers. Defaults
|
|
266
|
+
to option column.unit_spend.
|
|
267
|
+
target_item (str | float | list[str | float] | None, optional): A specific product
|
|
268
|
+
or list of products to focus the association analysis on. If None,
|
|
269
|
+
associations for all products are calculated. Defaults to None.
|
|
270
|
+
min_occurrences (int, optional): The minimum number of occurrences required for each product in the
|
|
271
|
+
association analysis. Defaults to 1. Must be at least 1.
|
|
272
|
+
min_cooccurrences (int, optional): The minimum number of co-occurrences required for the product pairs in
|
|
273
|
+
the association analysis. Defaults to 1. Must be at least 1.
|
|
274
|
+
min_support (float, optional): The minimum support value required for the association rules. Defaults to
|
|
275
|
+
0.0. Must be between 0 and 1.
|
|
276
|
+
min_confidence (float, optional): The minimum confidence value required for the association rules. Defaults
|
|
277
|
+
to 0.0. Must be between 0 and 1.
|
|
278
|
+
min_uplift (float, optional): The minimum uplift value required for the association rules. Defaults to 0.0.
|
|
279
|
+
Must be greater or equal to 0.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
pandas.DataFrame: A DataFrame containing the calculated association rules and their metrics.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ValueError: If the number of combinations is not 2 or 3, or if any of the minimum values are invalid.
|
|
286
|
+
ValueError: If the minimum support, confidence, or uplift values are outside the valid range.
|
|
287
|
+
ValueError: If the minimum occurrences or cooccurrences are less than 1.
|
|
288
|
+
|
|
289
|
+
Note:
|
|
290
|
+
The resulting DataFrame contains the following columns:
|
|
291
|
+
- product_1, product_2: The pair of products for which the association is calculated.
|
|
292
|
+
- occurrences_1, occurrences_2: The number of transactions containing each product.
|
|
293
|
+
- cooccurrences: The number of transactions containing both products.
|
|
294
|
+
- support: The proportion of transactions containing both products.
|
|
295
|
+
- confidence: The probability of buying product_2 given that product_1 was bought.
|
|
296
|
+
- uplift: The ratio of the observed support to the expected support if the products were independent.
|
|
297
|
+
"""
|
|
298
|
+
ProductAssociation._validate_minimum_values(
|
|
299
|
+
min_occurrences=min_occurrences,
|
|
300
|
+
min_cooccurrences=min_cooccurrences,
|
|
301
|
+
min_support=min_support,
|
|
302
|
+
min_confidence=min_confidence,
|
|
303
|
+
min_uplift=min_uplift,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Normalize target_item to a list for consistent processing
|
|
307
|
+
if target_item is not None:
|
|
308
|
+
if not isinstance(target_item, list):
|
|
309
|
+
target_item = [target_item]
|
|
310
|
+
|
|
311
|
+
# Validate that all items in target_item are of supported types
|
|
312
|
+
for item in target_item:
|
|
313
|
+
if not isinstance(item, str | float):
|
|
314
|
+
msg = f"target_item must contain only str or float values. Got {type(item)}"
|
|
315
|
+
raise TypeError(msg)
|
|
316
|
+
|
|
317
|
+
# Ensure target_item is not empty
|
|
318
|
+
if len(target_item) == 0:
|
|
319
|
+
raise ValueError("target_item cannot be an empty list")
|
|
320
|
+
|
|
321
|
+
if isinstance(df, pd.DataFrame):
|
|
322
|
+
df = ibis.memtable(df)
|
|
323
|
+
|
|
324
|
+
unique_transactions = df.select(_[group_col], _[value_col]).distinct()
|
|
325
|
+
total_transactions = unique_transactions.alias("t")[group_col].nunique().name("total_count")
|
|
326
|
+
|
|
327
|
+
product_occurrences = (
|
|
328
|
+
unique_transactions.group_by(value_col)
|
|
329
|
+
.aggregate(occurrences=_[group_col].nunique())
|
|
330
|
+
.mutate(occurrence_probability=_.occurrences / total_transactions)
|
|
331
|
+
.filter(_.occurrences >= min_occurrences)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
left_table = unique_transactions.rename({"item_1": value_col})
|
|
335
|
+
right_table = unique_transactions.rename({"item_2": value_col})
|
|
336
|
+
|
|
337
|
+
join_logic = [left_table[group_col] == right_table[group_col]]
|
|
338
|
+
if target_item is None:
|
|
339
|
+
join_logic.append(left_table.item_1 < right_table.item_2)
|
|
340
|
+
else:
|
|
341
|
+
join_logic.extend(
|
|
342
|
+
[
|
|
343
|
+
left_table.item_1 != right_table.item_2,
|
|
344
|
+
left_table.item_1.isin(target_item),
|
|
345
|
+
],
|
|
346
|
+
)
|
|
347
|
+
merged_df = left_table.join(right_table, predicates=join_logic)
|
|
348
|
+
|
|
349
|
+
cooccurrences = (
|
|
350
|
+
merged_df.group_by(["item_1", "item_2"])
|
|
351
|
+
.aggregate(cooccurrences=merged_df[group_col].nunique())
|
|
352
|
+
.mutate(support=_.cooccurrences / total_transactions)
|
|
353
|
+
.filter((_.cooccurrences >= min_cooccurrences) & (_.support >= min_support))
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
product_occurrences_1_rename = product_occurrences.rename(
|
|
357
|
+
{"item_1": value_col, "occurrences_1": "occurrences", "prob_1": "occurrence_probability"},
|
|
358
|
+
)
|
|
359
|
+
product_occurrences_2_rename = product_occurrences.rename(
|
|
360
|
+
{"item_2": value_col, "occurrences_2": "occurrences", "prob_2": "occurrence_probability"},
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
result = (
|
|
364
|
+
cooccurrences.join(product_occurrences_1_rename, "item_1")
|
|
365
|
+
.join(product_occurrences_2_rename, "item_2")
|
|
366
|
+
.mutate(
|
|
367
|
+
confidence=_.cooccurrences / _.occurrences_1,
|
|
368
|
+
uplift=_.support / (_.prob_1 * _.prob_2),
|
|
369
|
+
)
|
|
370
|
+
.filter(_.uplift >= min_uplift)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if target_item is None:
|
|
374
|
+
col_order = [
|
|
375
|
+
"item_1",
|
|
376
|
+
"item_2",
|
|
377
|
+
"occurrences_1",
|
|
378
|
+
"occurrences_2",
|
|
379
|
+
"cooccurrences",
|
|
380
|
+
"support",
|
|
381
|
+
"confidence",
|
|
382
|
+
"uplift",
|
|
383
|
+
]
|
|
384
|
+
inverse_pairs = result.mutate(
|
|
385
|
+
item_1=result.item_2,
|
|
386
|
+
item_2=result.item_1,
|
|
387
|
+
occurrences_1=result.occurrences_2,
|
|
388
|
+
occurrences_2=result.occurrences_1,
|
|
389
|
+
prob_1=result.prob_2,
|
|
390
|
+
prob_2=result.prob_1,
|
|
391
|
+
confidence=result.cooccurrences / result.occurrences_2,
|
|
392
|
+
)
|
|
393
|
+
result = result[col_order].union(inverse_pairs[col_order])
|
|
394
|
+
|
|
395
|
+
result = (
|
|
396
|
+
result.filter(result.confidence >= min_confidence)
|
|
397
|
+
.order_by(["item_1", "item_2"])
|
|
398
|
+
.rename({f"{value_col}_1": "item_1", f"{value_col}_2": "item_2"})
|
|
399
|
+
)
|
|
400
|
+
return result[
|
|
401
|
+
[
|
|
402
|
+
f"{value_col}_1",
|
|
403
|
+
f"{value_col}_2",
|
|
404
|
+
"occurrences_1",
|
|
405
|
+
"occurrences_2",
|
|
406
|
+
"cooccurrences",
|
|
407
|
+
"support",
|
|
408
|
+
"confidence",
|
|
409
|
+
"uplift",
|
|
410
|
+
]
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def df(self) -> pd.DataFrame:
|
|
415
|
+
"""Returns the executed DataFrame."""
|
|
416
|
+
if self._df is None:
|
|
417
|
+
self._df = self.table.execute().reset_index(drop=True)
|
|
418
|
+
return self._df
|