Loading...

Booking

This example uses the expedia dataset to predict, based on site activity, whether a user is likely to make a booking. You can download the dataset here.

  • cnt: Number of similar events in the context of the same user session.

  • user_location_city: The ID of the city in which the customer is located.

  • is_package: 1 if the click/booking was generated as a part of a package (i.e. combined with a flight), 0 otherwise.

  • user_id: ID of the user.

  • srch_children_cnt: The number of (extra occupancy) children specified in the hotel room.

  • channel: marketing ID of a marketing channel.

  • hotel_cluster: ID of a hotel cluster.

  • srch_destination_id: ID of the destination where the hotel search was performed.

  • is_mobile: 1 if the user is on a mobile device, 0 otherwise.

  • srch_adults_cnt: The number of adults specified in the hotel room.

  • user_location_country: The ID of the country in which the customer is located.

  • srch_destination_type_id: ID of the destination where the hotel search was performed.

  • srch_rm_cnt: The number of hotel rooms specified in the search.

  • posa_continent: ID of the continent associated with the site_name.

  • srch_ci: Check-in date.

  • user_location_region: The ID of the region in which the customer is located.

  • hotel_country: Hotel’s country.

  • srch_co: Check-out date.

  • is_booking: 1 if a booking, 0 if a click.

  • orig_destination_distance: Physical distance between a hotel and a customer at the time of search. A null means the distance could not be calculated.

  • hotel_continent: Hotel continent.

  • site_name: ID of the Expedia point of sale (i.e. Expedia.com, Expedia.co.uk, Expedia.co.jp, …).

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of vastorbit:

import vastorbit as vo

vo.__version__

Connect to VAST. This example uses an existing connection called VASTDSN. For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vo.connect("VASTDSN")

Let’s create a VastFrame of the dataset.

expedia = vo.read_csv("expedia.csv", parse_nrows = 1000)
expedia
📅
date_time
Timestamp(3)
100%
123
site_name
Integer
100%
123
posa_continent
Integer
100%
123
user_location_country
Integer
100%
123
user_location_region
Integer
100%
123
user_location_city
Integer
100%
123
orig_destination_distance
Double
37%
123
user_id
Integer
100%
123
is_mobile
Integer
100%
123
is_package
Integer
100%
123
channel
Integer
100%
📅
srch_ci
Date
99%
📅
srch_co
Date
99%
123
srch_adults_cnt
Integer
100%
123
srch_children_cnt
Integer
100%
123
srch_rm_cnt
Integer
100%
123
srch_destination_id
Integer
100%
123
srch_destination_type_id
Integer
100%
123
is_booking
Integer
100%
123
cnt
Integer
100%
123
hotel_continent
Integer
100%
123
hotel_country
Integer
100%
123
hotel_market
Integer
100%
123
hotel_cluster
Integer
100%
12014-09-30 17:23:112423505703[null]28540002014-10-062014-10-14101880310131516958
22014-09-30 17:27:052816833522273[null]29010052015-01-162015-01-17105220746016204146382
32014-09-30 17:30:46231136519156132.121341430092014-10-102014-10-12201140396016105358
42014-09-30 17:37:123716958540970[null]18321192014-10-202014-10-241012195860166827568
52014-09-30 17:43:002423643169[null]31950052014-10-012014-10-05101122096026701977
62014-09-30 18:05:023716976141949[null]30450092014-10-012014-10-0210122799601677205
72014-09-30 18:45:41236646249272874.72982581192015-05-282015-05-3110182501012506281
82014-09-30 18:47:0834320535412328137.644534661092014-10-172014-10-1941141556102219839542
92014-09-30 18:51:2034320535412328141.150234661092014-10-172014-10-1941141556101219839591
102014-09-30 19:05:142366258168351994.99736180192014-12-262015-01-02221114391014163150365
112014-09-30 19:06:142366258168351995.60336180192014-12-262015-01-02221114391044163150365
122014-09-30 19:11:522366258168351990.335236180192014-12-272015-01-03221114391014163150352
132014-09-30 19:12:492366258168351990.672436180192014-12-272015-01-03221114391014163150352
142014-09-30 19:13:482366258168351993.347136180192014-12-272015-01-03221114391014163150387
152014-09-30 19:33:212366174241035447.53337180192014-12-242015-01-0130182531026701995
162014-09-30 19:43:1523661895592645.966716741092014-12-202014-12-21121826710125067569
172014-09-30 19:46:203716955833389[null]26671062014-11-282014-11-304021224560262042711
182014-09-30 20:41:302423505703[null]13050122014-12-292015-01-02101822010231824658
192014-09-30 20:49:052366174492581899.289120890092014-10-112014-10-12201821611125035011
202014-09-30 20:53:022366174472875935.469434600022014-10-202014-10-2210182931016463943

Warning

This example uses a sample dataset. For the full analysis, you should consider using the complete dataset.

Data Exploration and Preparation

Sessionization is the process of gathering clicks for a certain period of time. We usually consider that after 30 minutes of inactivity, the user session ends (date_time - lag(date_time) > 30 minutes). For these kinds of use cases, aggregating sessions with meaningful statistics is the key for making accurate predictions.

We start by using the sessionize() method to create the variable session_id. We can then use this variable to aggregate the data.

expedia.sessionize(
    ts = "date_time",
    by = ["user_id"],
    session_threshold = "30 minutes",
    name = "session_id",
)
📅
date_time
Timestamp(3)
100%
123
site_name
Integer
100%
123
posa_continent
Integer
100%
123
user_location_country
Integer
100%
123
user_location_region
Integer
100%
123
user_location_city
Integer
100%
123
orig_destination_distance
Double
37%
123
user_id
Integer
100%
123
is_mobile
Integer
100%
123
is_package
Integer
100%
123
channel
Integer
100%
📅
srch_ci
Date
99%
📅
srch_co
Date
99%
123
srch_adults_cnt
Integer
100%
123
srch_children_cnt
Integer
100%
123
srch_rm_cnt
Integer
100%
123
srch_destination_id
Integer
100%
123
srch_destination_type_id
Integer
100%
123
is_booking
Integer
100%
123
cnt
Integer
100%
123
hotel_continent
Integer
100%
123
hotel_country
Integer
100%
123
hotel_market
Integer
100%
123
hotel_cluster
Integer
100%
123
session_id
Bigint
100%
12014-02-08 19:20:312366442301041658.195981102014-03-162014-03-1910112273601250663391
22014-03-26 10:39:48236644230104450.407380192014-04-212014-04-242018266101250411482
32014-03-26 11:53:51236644230104453.598780192014-04-292014-05-022018266101250411103
42014-03-26 11:56:10236644230104454.102680192014-04-292014-05-022018266101250411183
52014-03-26 12:01:10236644230104453.598780192014-04-282014-05-012018266101250411103
62014-03-26 12:04:01236644230104454.102680192014-04-282014-05-012018266101250411183
72014-03-26 12:14:31236644230104454.102680192014-04-132014-04-162018266101250411183
82014-03-26 12:19:10236644230104453.797380192014-04-132014-04-16201826610125041143
92014-04-06 23:04:59236644230104453.4980152014-04-212014-04-222018266101250411724
102014-04-06 23:13:49236644230104453.34380152014-04-212014-04-222018266104250411974
112014-04-19 12:30:502366442301041318.273280192014-05-052014-05-0710111569101250623895
122014-04-19 13:11:532366442301041318.118580192014-05-122014-05-1410111569101250623796
132014-04-21 09:53:492366442301041318.118580192014-05-122014-05-1510111569101250623797
142014-08-13 20:31:04236633760851069.077580192014-10-052014-10-0720111353101250699598
152014-08-13 20:53:48236633760851066.757280192014-10-052014-10-0720111353101250699108
162014-08-16 21:34:33236633760857.484380092014-08-202014-08-272018267101250674239
172014-08-16 21:35:14236633760857.484380092014-08-202014-09-032018267101250674239
182014-08-16 21:43:252366337608516.731180092014-08-202014-08-272018267101250675239
192014-08-18 06:11:31236633760857.484380092014-08-192014-08-2620182671012506742310
202014-09-11 21:37:222366337179420.698380022014-09-122014-09-1420182671012506767611

The duration of the trip should also influence/be indicative of the user’s behavior on the site, so we’ll take that into account.

expedia["trip_duration"] = expedia["srch_co"] - expedia["srch_ci"]

If a user looks at the same hotel several times, then it might mean that they’re looking to book that hotel during the session.

expedia.analytic(
    "mode",
    columns = "hotel_cluster",
    by = [
        "user_id",
        "session_id",
    ],
    name = "mode_hotel_cluster",
    add_count = True,
)
📅
date_time
Timestamp(3)
100%
123
site_name
Integer
100%
123
posa_continent
Integer
100%
123
user_location_country
Integer
100%
123
user_location_region
Integer
100%
123
user_location_city
Integer
100%
123
orig_destination_distance
Double
37%
123
user_id
Integer
100%
123
is_mobile
Integer
100%
123
is_package
Integer
100%
123
channel
Integer
100%
📅
srch_ci
Date
99%
📅
srch_co
Date
99%
123
srch_adults_cnt
Integer
100%
123
srch_children_cnt
Integer
100%
123
srch_rm_cnt
Integer
100%
123
srch_destination_id
Integer
100%
123
srch_destination_type_id
Integer
100%
123
is_booking
Integer
100%
123
cnt
Integer
100%
123
hotel_continent
Integer
100%
123
hotel_country
Integer
100%
123
hotel_market
Integer
100%
123
hotel_cluster
Integer
100%
123
session_id
Bigint
100%
📅
trip_duration
Interval day to second
99%
123
mode_hotel_cluster
Integer
100%
123
mode_hotel_cluster_count
Bigint
100%
12014-08-27 10:03:532366318316465380.631970122015-01-302015-02-05201792101620814806416 days, 0:00:006464
22014-08-27 10:01:092366318316465380.631970022014-10-312014-11-01201792101620814806411 day, 0:00:006464
32014-08-27 09:58:022366318316465378.691870022015-01-302015-02-05201792102620814806416 days, 0:00:006464
42014-08-27 09:51:322366318316465378.691870022015-01-292015-02-082017921026208148064110 days, 0:00:006464
52014-08-27 09:45:422366318316465378.691870022014-10-312014-11-01201792105620814806411 day, 0:00:006464
62014-08-27 09:58:552366318316465375.904570022014-10-312014-11-01201792101620814808611 day, 0:00:006464
72014-09-10 12:20:392366318316463936.106270022014-10-122014-10-131012261660162041452521 day, 0:00:0055
82014-09-24 11:57:492366318878830.259170192014-12-042014-12-0720119535101250593533 days, 0:00:0055
92014-09-24 11:36:082366318878830.259170092014-12-042014-12-0720119535111250593533 days, 0:00:0055
102014-09-24 11:23:592366318878830.259170092014-12-042014-12-0720119535102250593533 days, 0:00:0055
112014-10-06 18:28:52236631833705826.944370002014-12-042014-12-07201195351012505939143 days, 0:00:009191
122014-10-06 18:32:36236631833705826.999470002014-12-042014-12-0720119535101250593543 days, 0:00:009191
132014-10-29 05:57:4423663188031862.759970022014-11-112014-11-12201195351022505933251 day, 0:00:003232
142014-10-29 05:58:4423663188031862.733770022014-11-112014-11-1220119535101250593751 day, 0:00:003232
152014-10-31 07:14:0323663188031863.078870142014-12-042014-12-07201195351062505939063 days, 0:00:009090
162014-11-06 10:14:59236631841478[null]70122014-12-042014-12-07201195351012505933273 days, 0:00:003232
172014-11-06 10:44:50236631841478[null]70122014-12-042014-12-07201195351012505932873 days, 0:00:003232
182014-11-06 10:45:59236631841478[null]70122014-12-042014-12-07201195351012505934273 days, 0:00:003232
192014-11-06 10:47:25236631841478[null]70122014-12-042014-12-07201195351012505937673 days, 0:00:003232
202014-11-06 11:27:14236631841478[null]70122014-12-042014-12-07201195351012505933383 days, 0:00:003333

We can now aggregate the session and get some useful statistics out of it:

  • end_session_date_time: Date and time when the session ends.

  • session_duration: Session duration.

  • is_booking: 1 if the user booked during the session, 0 otherwise.

  • trip_duration: Trip duration.

  • orig_destination_distance: Average of the physical distances between the hotels and the customer.

  • srch_family_cnt: The number of people specified in the hotel room.

import vastorbit.sql.functions as fun

expedia = expedia.groupby(
    columns = [
        "user_id",
        "session_id",
        "mode_hotel_cluster_count",
    ],
    expr = [
        fun.max(expedia["date_time"])._as("end_session_date_time"),
        "EXTRACT(SECOND FROM MAX(date_time) - MIN(date_time)) AS session_duration",
        fun.max(expedia["is_booking"])._as("is_booking"),
        "EXTRACT(DAY FROM AVG(trip_duration)) AS trip_duration",
        fun.avg(expedia["orig_destination_distance"])._as("avg_distance"),
        fun.sum(expedia["cnt"])._as("nb_click_session"),
        fun.median(expedia["srch_children_cnt"] + expedia["srch_adults_cnt"])._as("srch_family_cnt"),
    ],
)
expedia
123
user_id
Integer
100%
123
session_id
Bigint
100%
123
mode_hotel_cluster_count
Integer
100%
📅
end_session_date_time
Timestamp(3)
100%
123
session_duration
Bigint
100%
123
is_booking
Integer
100%
123
trip_duration
Bigint
99%
123
avg_distance
Double
38%
123
nb_click_session
Bigint
100%
123
srch_family_cnt
Bigint
100%
191702014-01-26 23:45:0716052353.924499999999691
292872014-01-27 00:36:290142354.144711
3151402014-09-12 23:11:450032366.326422
4152542014-11-14 12:50:33002190.916512
5153422014-11-17 14:04:53002364.564312
6154542014-11-18 10:38:06002184.207512
7155542014-11-19 09:25:32002190.916522
8156492014-11-21 07:57:350047146.576111
9157542014-11-21 14:28:193203303.650352
10158542014-11-24 14:30:182402148.499766666666772
11159542014-11-24 16:43:205602184.7152999999999832
121510542014-12-03 23:42:481002185.087322
131511332014-12-08 22:49:0400177.529112
141512542014-12-10 22:13:111713187.386542
151513482014-12-12 11:17:2000182.647122
161514912014-12-15 10:35:47003748.028112
171515542014-12-15 20:38:21002184.207512
181516722014-12-17 11:54:40901365.22547542
191517542014-12-20 11:40:25003187.386512
20261592014-08-07 08:28:48121113.38730000000000232

Let’s look at the missing values.

expedia.count_percent()
countpercent
"user_id"46987.0100.0
"session_id"46987.0100.0
"mode_hotel_cluster_count"46987.0100.0
"end_session_date_time"46987.0100.0
"session_duration"46987.0100.0
"is_booking"46987.0100.0
"nb_click_session"46987.0100.0
"srch_family_cnt"46987.0100.0
"trip_duration"46872.099.755
"avg_distance"18219.038.775

Let’s impute the missing values for avg_distance and trip_duration.

expedia["avg_distance" ].fillna(method = "avg")
expedia["trip_duration"].fillna(method = "avg")
123
user_id
Integer
100%
123
session_id
Bigint
100%
123
mode_hotel_cluster_count
Integer
100%
📅
end_session_date_time
Timestamp(3)
100%
123
session_duration
Bigint
100%
123
is_booking
Integer
100%
123
trip_duration
Real
100%
123
avg_distance
Real
100%
123
nb_click_session
Bigint
100%
123
srch_family_cnt
Bigint
100%
1131642014-07-09 12:04:534714.01979.865036013334581
2132642014-07-10 05:42:46006.01979.865036013334571
3133642014-07-11 06:55:202106.01979.8650360133345121
4134642014-07-11 07:40:33006.01979.865036013334511
5135642014-07-14 09:36:155212.01979.865036013334532
6136642014-07-15 09:01:43003.01979.865036013334522
713782014-07-16 07:02:12000.01979.865036013334512
8138642014-07-17 13:33:443602.01979.865036013334552
9139642014-07-17 20:53:38003.01979.865036013334512
101310642014-07-17 22:16:18003.01979.865036013334512
111311642014-07-20 15:27:20003.01979.865036013334512
121312642014-07-21 07:26:26003.01979.865036013334512
131313642014-07-22 06:47:22903.01979.865036013334522
141314462014-08-18 13:14:504504.04880.027433333333141
151315822014-08-18 15:05:221803.04881.464162
161316822014-08-19 10:23:124803.04880.41435102
171317582014-08-19 14:14:091503.04925.120122
181318252014-08-21 04:07:525002.01979.8650360133345102
19131952014-08-23 04:16:452215.01979.8650360133345252
201320462014-08-23 04:50:09005.01979.865036013334512

We can then look at the links between the variables. We will use Spearman’s rank correleation coefficient to get all the monotonic relationships.

expedia.corr(method = "spearman")

We can see huge links between some of the variables (mode_hotel_cluster_count and session_duration) and our response variable (is_booking). A logistic regression would work well in this case because the response and predictors have a monotonic relationship.

Machine Learning

Let’s create our LogisticRegression model.

from vastorbit.machine_learning.vast import LogisticRegression

model_logit = LogisticRegression(
    max_iter = 1000,
)
model_logit.fit(
    expedia,
    [
        "avg_distance",
        "session_duration",
        "nb_click_session",
        "mode_hotel_cluster_count",
        "session_id",
        "srch_family_cnt",
        "trip_duration",
    ],
    "is_booking",
)

None of our coefficients are rejected (pvalue = 0). Let’s look at their importance.

model_logit.features_importance()

It looks like there are two main predictors: mode_hotel_cluster_count and trip_duration. According to our model, users likely to make a booking during a particular session will tend to:

  • look at the same hotel many times.

  • look for a shorter trip duration.

  • not click as much (spend more time at the same web page).

Let’s add our prediction to the VastFrame.

model_logit.predict_proba(
    expedia,
    name = "booking_prob_logit",
    pos_label = 1,
)
123
user_id
Integer
100%
123
session_id
Bigint
100%
123
mode_hotel_cluster_count
Integer
100%
📅
end_session_date_time
Timestamp(3)
100%
123
session_duration
Bigint
100%
123
is_booking
Integer
100%
123
trip_duration
Real
100%
123
avg_distance
Real
100%
123
nb_click_session
Bigint
100%
123
srch_family_cnt
Bigint
100%
123
booking_prob_logit
Double
100%
171642014-08-27 10:03:531104.05378.873951220.16251318890936553
27252014-09-10 12:20:39001.03936.1062110.2070044481824488
37352014-09-24 11:57:495013.0830.2590999999999420.38692915542922157
474912014-10-06 18:32:364403.0826.97185220.30547100062432875
575322014-10-29 05:58:44001.0862.7468320.1923126340027882
676902014-10-31 07:14:03003.0863.0788620.13575038197798783
777322014-11-06 10:47:252603.01979.8650360133345420.2395448571436353
878332014-11-06 11:27:473303.01979.8650360133345420.2716918969593289
979732014-11-07 11:03:11603.01979.8650360133345320.14598914360410956
10710322014-11-10 16:27:13005.01979.8650360133345120.09530501381912733
11711772014-11-12 11:40:284301.01979.8650360133345220.3749680936647942
12712202014-11-20 07:56:48607.05457.39715220.07629242631229817
13713502014-11-20 09:02:53003.0866.3349120.12846550135449503
14714322014-12-01 15:07:49003.0862.3329120.13179467441684575
15715912014-12-18 11:04:142702.0616.5975000000001220.2467497435557924
1671662014-12-18 12:16:29002.0628.2538120.1598834412013379
1771762014-12-18 13:19:09002.0628.2538120.15923153625324338
18141412014-03-16 16:43:384512.0181.060661920.4665229486982773
19142452014-04-27 19:44:215702.02266.2161710.4970769412396686
20143552014-05-27 18:24:421302.02477.112775420.20864304603563083

While analyzing the following boxplot (prediction partitioned by is_booking), we can notice that the cutoff is around 0.22 because most of the positive predictions have a probability between 0.23 and 0.5. Most of the negative predictions are between 0.05 and 0.2.

expedia["booking_prob_logit"].boxplot(by = "is_booking")

Let’s confirm our hypothesis by computing the best cutoff.

model_logit.score(metric = "best_cutoff")

Let’s look at the efficiency of our model with a cutoff of 0.22.

model_logit.report(cutoff = 0.22)
value
auc0.7086731231019248
prc_auc0.7480492842247632
accuracy0.6786770808947156
log_loss0.47390987496962017
precision0.3551469766402146
recall0.6410127092999798
f1_score0.4570627157652474
mcc0.2770804435239417
informedness0.32976193380298735
markedness0.2328151442406683
csi0.2962289656458304

ROC Curve:

model_logit.roc_curve()

We’re left with an excellent model. With this, we can predict whether a user will book a hotel during a specific session and make adjustments to our site accordingly. For example, to influence a user to make a booking, we could propose new hotels.

Conclusion

We’ve solved our problem in a pandas-like way, all without ever loading data into memory!