This repository has been archived by the owner on Aug 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbuyingbehavior.rb
211 lines (176 loc) · 6.02 KB
/
buyingbehavior.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
require 'pp'
require 'gaussian'
require 'logit'
require 'features'
class BuyingBehavior
attr_reader :expression, :variables
def initialize(items, behavior_settings)
@items = items
@behavior_settings = behavior_settings
puts "available items in buying behavior: #{@items.size}" if $debug
end
def buy_first
validate_max_price(@items.first)
end
def buy_random
validate_max_price(@items.sample)
end
def buy_cheap
validate_max_price(@items.min_by { |item| item['price'] })
end
def buy_n_cheap(n)
n.times do
item = buy_cheap
return nil if item.nil?
@items.delete(item)
end
buy_cheap
end
def buy_second_cheap
buy_n_cheap(1)
end
def buy_third_cheap
buy_n_cheap(2)
end
def buy_cheap_and_prime
validate_max_price(having_prime(@items).min_by { |item| item['price'] })
end
def buy_expensive
validate_max_price(@items.max_by { |item| item['price'] })
end
def buy_cheapest_best_quality_with_prime
@items = having_prime(@items)
buy_cheapest_best_quality
end
def buy_cheapest_best_quality
best_quality = @items.map { |item| item['quality'] }.min
best_quality_items = @items.select { |item| item['quality'] == best_quality }
validate_max_price(best_quality_items.min_by { |item| item['price'] })
end
def buy_logit_coefficients
theta = @behavior_settings['coefficients'].map { |_key, value| value }
probs = []
@items.each do |item|
names = @behavior_settings['coefficients'].map { |key, _value| key }
names.delete('intercept')
features = [build_features_array(names, item)]
logit = Logit.new
y = []
features.length.times { y.push(1) }
prob = logit.predict(features, theta, y)
probs.push(prob)
end
if $debug
logit_model = Hash.new
for i in (0..probs.length-1)
break if probs.nil?
log_item = @items[i]
log_item["probability_of_sell"] = probs[i]
logit_model[@items[i]["offer_id"]] = log_item
end
logit_model["global"] = {}
logit_model["global"]["amount_of_all_competitors"] = @items.length
logit_model["global"]["highest_prob_price_rank"] = 1 + @items.select { |item| item['price'] < @items[probs.index(probs.max)]['price'] }.size
#Logit.info logit_model.to_json
end
validate_max_price(normalize_and_roll_dice_with(probs))
end
def buy_prefer_cheap
relevant_offers = @items.select{|item| item['price'] <= @behavior_settings['max_buying_price']}
if relevant_offers.length == 0
nil
else
probabilites = buy_probabilities(relevant_offers)
choose_weighted(relevant_offers.zip(probabilites))
end
end
def buy_scoring_based
# Compare with paper: "Dynamic Pricing under Competition on Online Marketplaces: A Data-Driven Approach"
# The scoring factors for price and quality are different for each consumer. A lower score is better.
# We generate a random factor within a specified value range.
price_factor_range = 1.0..1.0
quality_factor_range = 0.0..3.0
willingness_to_buy_range = 20.0..80.0
prng = Random.new
price_factor = prng.rand(price_factor_range)
quality_factor = prng.rand(quality_factor_range)
willingness_to_buy = prng.rand(willingness_to_buy_range)
def score(item, price_factor, quality_factor)
price_factor * item['price'] + quality_factor * item['quality']
end
scored_items = @items.map{|item| {item: item, score: score(item, price_factor, quality_factor)} }
best_scored_item = scored_items.min { |a, b| a[:score] <=> b[:score] }
if $debug
puts "Price factor #{price_factor}, Quality factor #{quality_factor}, Willingness score: #{willingness_to_buy}"
puts "Scored items:"
scored_items.sort { |a, b| a[:score] <=> b[:score] }.each {|element| puts "\t#{element}"}
end
# Only buy from the best offer if its score is lower or equal than the willingness to buy.
if best_scored_item[:score] <= willingness_to_buy
best_scored_item[:item]
else
nil
end
end
private
# Uses a modified market power formula to calculate buying probabilities.
# The cheapest offers has the highest probability.
# The higher the price difference to the cheapest offers, the lower is the buying probability.
def buy_probabilities(offers)
price_sensitivity = 1
prices = offers.map{|offer| offer['price']}
max_price = prices.max
price_sum = prices.reduce(0, :+)
probabilities = []
offers.each do |offer|
probability = (max_price + price_sensitivity - offer['price']) / (offers.length * (max_price + price_sensitivity) - price_sum)
probabilities.push(probability)
end
probabilities
end
def build_features_array(feature_names, item)
result = []
feature_names.each do |feature|
result.push(Features.new.determine(feature, @items, item))
end
result
end
def normalize_and_roll_dice_with(probs)
sumProbs = probs.inject(:+)
return nil if sumProbs == 0
normalized_probs = probs.map { |p| p / sumProbs }
r = Random.rand
currentSum = 0
for i in (0..normalized_probs.length - 1)
currentSum += normalized_probs[i]
if r <= currentSum
selected_item = @items[i]
break
end
end
selected_item
end
# consumes { :black => 51, :white => 17 }
def choose_weighted(weighted)
sum = weighted.inject(0) do |sum, item_and_weight|
sum += item_and_weight[1]
end
target = Random.rand(sum)
weighted.each do |item, weight|
return item if target <= weight
target -= weight
end
end
def validate_max_price(item)
return nil if item.nil? || item.blank?
if item['price'] > @behavior_settings['max_buying_price']
puts "item price (#{item['price']}€) is above max_buying_price (#{@behavior_settings['max_buying_price']}€), reject" if $debug
nil
else
item
end
end
def having_prime(items)
items.select { |item| item['prime'] == true }
end
end