+13

EDA dữ liệu cuộc thi Bookingchallenge và Baseline model

Xin chào mọi người, cách đây khoảng 2 tháng mình có tham gia một cuộc thi về recommendation system do Booking.com tổ chức, hôm nay mình sẽ chia sẻ bài viết về cách mình đã phân tích dữ liệu như thế nào, bên cạnh đó cũng đưa ra mô hình baseline của mình.

Booking challenge

Cuộc thi này được booking.com tổ chức dựa trên dữ liệu về lịch sử book phòng của khách hàng khi đi du lịch đến một thành phố khác, mục đích của cuộc thi là đưa ra được thành phố tiếp theo mà khách hàng sẽ đến để booking có thể dễ dàng đoán đúng và gợi ý địa điểm tiếp theo. Để biết thêm về cuộc thi các bạn có thể theo dõi tại link này

Data

Dữ liệu được cung cấp bởi booking.com và dữ liệu này theo mình thấy thì rất là clean rồi 😄. Dữ liệu gồm các trường dưới đây: user_id - User ID

check-in - Thời gian check-in

checkout -Thời gian check out

affiliate_id - Một nơi (website, trang tìm kiếm, ...) mà khách hàng click vào trước khi vào booking.com giống những thông tin quảng cáo mn hay thấy về shopee hay tiki trên FB thì FB sẽ là trường này được mã hóa

device_class - desktop/mobile

booker_country - Thành phố tại nơi lúc mà khách hàng thực hiện đặt phòng

hotel_country - Đất nước của khách sạn bạn đặt

city_id - thành phố nơi khách sạn bạn đặt

utrip_id - Nhận dạng duy nhất của chuyến đi của khách hàng

EDA data

EDA bằng 1 dòng code

Như mình đã từng viết ở bài Exploring dữ liệu chỉ một dòng code trong python thì với dữ liệu của cuộc thi này chúng ta cũng có thể sử dụng để EDA dữ liệu như dưới đây.

import pandas as pd
import pandas_profiling

Đọc data train

data = pd.read_csv('booking_train_set.csv')
profile = pandas_profiling.ProfileReport(data, title='Pandas Profiling Booking Data Report', explorative=True)
profile

Dùng pandas_profiling để phân tích dữ liệu của cuộc thi bookingchallenge.com trong một dòng code kết quả thu được như sau:

Hình1: EDA dữ liệu bằng pandas_profiling

Các bạn thử down dữ liệu tại đây về rồi thử xem nhé.

Phân tích dữ liệu

Như ở trên mình thử test thử với một dòng code thì căn bản chúng ta có thể thấy data của cuộc thi này khá là clean và cũng không có missing data vì vậy chúng ta không cần phải giải quyết vấn đề missing data ở đây nữa 😄. Trước tiên chúng ta cùng xem xem shape của tập train và tập test là bao nhiêu nhé.

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# read train, test data
train = pd.read_csv('booking_train_set.csv', dtype={"user_id": str, "city_id": str, 'affiliate_id': str,
                       'utrip_id': str},date_parser=['checkin', 'checkin'])
test = pd.read_csv('booking_test_set.csv')
# check shape train, test data
print(train.shape, test.shape)

Shape của train : (1166835, 10) ~ 1166835 hàng và 10 cột shape của test: (378667, 11), 378667 hàng và 11 cột

Hình 1: train, test data

ở đây trong tập test có thêm 2 columns khác với tập train: 'row_num', 'total_rows' tuy nhiên 2 columns này không ảnh hưởng gì đến kết quả cả Tiếp theo chúng ta sẽ thử xem dữ liệu này chứa missing data hay không?

train.info()

Hình2: check missing data Dựa vào hình 2 chúng ta có thể nhận thấy rằng dữ liệu train của cuộc thi này khá là sạch và không có giá trị missing luôn vậy cũng tốt đỡ mất công điền giá trị bị missing =))

Tiếp tục phân tích dữ liệu để xem xem số lượng user_id, city_id, booker_country, hotel_country trong dữ liệu train là bao nhiêu?

# number of user_id
len(train.user_id.unique())
# number of city_id
len(train.city_id.unique())
# number of booker_country
len(train.booker_country.unique())
# number of hotel_country
len(train.hotel_country.unique())
# number of utrip_id
len(train.utrip_id.unique())

Số lượng user_id ở đây là 200153

Số lượng city_id: 39901

Số lượng booker_country: 5

Số lượng hotel_country: 195

Số lượng utrip_id: 217686

Dựa vào số lượng utrip_id và city_id nếu như chúng ta dùng multiclass để classification 39901 class này khá là vất vả 😛. Vì vậy mình lựa chọn baseline là Matrix Factorization để gợi ý thì mình sẽ tính toán điểm tương đồng của các city_id với nhau để gợi ý cho từng user.

Plot Số lượng device_class.

train.device_class.value_counts().plot(kind='bar')

Hình: số lượng device_class

Plot số lượng booker_country

train.booker_country.value_counts().plot(kind='bar')

Hình: số lượng booker_country

Bây giờ chúng ta sẽ groupby user_id theo checkin để xem số lần check in của từng user_id là bao nhiêu trong tập dữ liệu mà ban tổ chức cung cấp.

# groupby user_id follow checkin
train_user_group = train.groupby('user_id').count()[['checkin']].sort_values('checkin',ascending=False)

Kiểm tra số lần checkin nhiều nhất trong các user_id là bao nhiêu

train_user_group.checkin.max()
# result: 172

Số lần checkin nhiều nhất là 172 lần, check xem thử xem user_id nào có số lượng checkin này nhiều nhất.

train_user_group[train_user_group['checkin'] == 172]

Vậy chúng ta có thể thấy user_id = 2209265 có số lượng lần checkin nhiều nhất là 172 lần. Thử vẽ biểu đồ xem phân bố của lượt checkin như thế nào nhé.

sns.boxplot(x="checkin", data=train_user_group)

Hình: boxplot checkin của user_id

Dựa vào biểu đồ trên chúng ta có thể thấy rằng số lần checkin của từng user_id hầu hết ở trong (0, 75), còn số lượng lớn hơn 75 thì rất ít.

Tính số ngày ở tại các city_id của từng user_id.

train['checkin'] = pd.to_datetime(train['checkin'])
train['checkout'] = pd.to_datetime(train['checkout'])

train['duration'] = (train['checkout'] - train['checkin']).dt.days
train.head()

Hình: data train

train_group_checkin = train.groupby('checkin').agg({'user_id': 'count', 'duration': 'mean'})

Hình: train_group_checkin

Vẽ biểu đồ quan hệ giữa checkin và user_id

g = sns.relplot(
    data=train_group_checkin,
    x="checkin", y="user_id", kind="line", height=8
)

Hình: relative of user_id and checkin

Groupby city_id theo checkin và duration để tính xem mỗi city_id được checkin bao nhiêu lần.

train_city_group = train.groupby('city_id').agg({'checkin': 'count', 'duration': 'mean'})\
                    .sort_values('checkin',ascending=False)

Kiểm tra xem city_id nào được checkin nhiều nhất:

train_city_group[train_city_group['checkin'] == train_city_group.checkin.max()]

Result: city_id: 47499 checkin 11242 lần. Thử vẽ biểu đồ xem phân bố của lượt checkin đối với city_id như thế nào nhé.

sns.boxplot(x="checkin", data=train_city_group)

Hình: boxplot checkin của city_id

Dựa vào hình trên chúng ta có thể thấy rằng lượng checkin chủ yếu nằm trong khoảng từ 0 cho đến 6000 còn lớn hơn 6000 thì khác là ít

Tiếp theo chúng ta kiểm tra những chuyển đi đến city_id trong mỗi tháng.

train['checkin_month'] = train.checkin.dt.month
# groupby city_id and checkin_month follow checkin and duration
train_city_month_group = train.groupby(['city_id', 'checkin_month']).agg({'checkin': 'count', 'duration': 'mean'})\
                    .reset_index().sort_values(['city_id', 'checkin_month', 'checkin'],ascending=False)

Hình: train_city_month_group data

# Initialize a grid of plots with an Axes for each walk
city_idx = list(train_city_group.index)[:10]
df_plot  = train_city_month_group[train_city_month_group.city_id.isin(city_idx)]

grid     = sns.FacetGrid(df_plot, col="city_id", hue="city_id", palette="tab20c",
                     col_wrap=5, height=2.5)
grid.map(plt.plot, "checkin_month", "checkin", marker="o")
grid.fig.tight_layout(w_pad=1)

Hình: plot 10 city_id

Ở trên đây mình đã phân tích dữ liệu train rồi mọi nguời có thể tham khảo colab ở đây nhé . Tiếp theo chúng ta sẽ đến với mô hình baseline nhé ^^.

Baseline model

City_id Population

Idea cho tìm ra thành phố phổ biến nhất khá là đơn giản.

  • Đầu tiên mình tìm thành phố phổ biến nhất trong từng hotel_country
  • Sau đó với tập test: Với mỗi utrip_id mình kiểm tra xem user_id đó trong utrip_id này đã đi đến hotel_country nào nhiều nhất, => thành phố tiếp theo chính là thành phố phổ biến nhất trong hotel_country đó.
# list hotel_country 
list_hotel_country = data.hotel_country.unique()
len(list_hotel_country)

Chúng ta có tổng cộng 195 hotel_country, tiếp theo sẽ tìm các city phổ biến trong từng hotel_country.

popular_city = []
for i in range(len(list_hotel_country)):
  # print(list_hotel_country[i])
  data_hotel = data[data.hotel_country == list_hotel_country[i]]
  z = data_hotel.city_id.value_counts()
  z1 = z.to_dict() #converts to dictionary
  data_hotel['count_city'] = data_hotel['city_id'].map(z1) 
  popular_city.append(data_hotel[data_hotel.count_city == data_hotel.count_city.max()]\
          [['city_id', 'count_city']].drop_duplicates().city_id.to_list()[0])
# Create dataframe popular_city
popular_data = pd.DataFrame(
                   columns=['hotel_country', 'city_id'])
popular_data['hotel_country'] = list_hotel_country.tolist()
popular_data['city_id'] = popular_city

Cùng dự đoán thành phố tiếp theo cho tập test data của bookingchallenge.com nhé. Đầu tiên đọc dữ liệu test :

test = pd.read_csv('booking_test_set.csv')

trong data test này mỗi utrip_id sẽ có một thành phố là Null vì vậy trước hết chúng ta sẽ loại bỏ thành phố null và giữ lại những thành phố not null trước nhé.

test_data_nona = test[test.hotel_country.notna()]

Tiếp theo đây chúng ta sẽ dự đoán thành phố tiếp theo mà user sẽ đi tới nhé.

city_id_pred = []
for i in range(len(list_utrip_id)):
  test_hotel = test_data_nona[test_data_nona.utrip_id == list_utrip_id[i]]
  z = test_hotel.hotel_country.value_counts()
  z1 = z.to_dict() #converts to dictionary
  test_hotel['count_hotel'] = test_hotel['hotel_country'].map(z1) 
  hotel = test_hotel[test_hotel.count_hotel == test_hotel.count_hotel.max()]\
          [['hotel_country', 'count_hotel']].drop_duplicates().hotel_country.to_list()[0]
  city_id_pred.append(popular_data[popular_data.hotel_country==hotel].city_id.to_list()[0])

Như vậy chúng ta đã dự đoán được thành phố tiếp theo dựa vào thành phố phổ biến của từng hotel_country như idea mình đã đưa ra ở trên, mọi người thử chạy theo nhé 😄.

Mô Hình dự đoán city tiếp theo

Mô hình baseline mình dùng là mô hình MF dùng thư viện implicit của benfred

Đầu tiên import thư viện đã nhỉ

import pandas as pd
import numpy as np
import datetime
train_data = pd.read_csv("booking_train_set.csv")
test_data = pd.read_csv("booking_test_set.csv")
# get test data is  nan
test_data_na = test_data[test_data.hotel_country.isna()]
# get test_data is not na
test_data_nona = test_data[test_data.hotel_country.notna()]

train_data = train_data[[ 'user_id', 'checkin', 'checkout', 'city_id',
       'device_class', 'affiliate_id', 'booker_country', 'hotel_country',
       'utrip_id']]
       
 test_data_nona_1 = test_data_nona[[ 'user_id', 'checkin', 'checkout', 'city_id',
       'device_class', 'affiliate_id', 'booker_country', 'hotel_country',
       'utrip_id']]
       
     # concat train an test data
  total_data = pd.concat([train_data, test_data_nona_1])

Sau khi đã concat data train và data test xong thì chúng ta sẽ tính số lượng utrip_id trong 1 chuyến đi đã di chuyển đến bao nhiêu thành phố.

z = total_data.utrip_id.value_counts()

z1 = z.to_dict() #converts to dictionary

total_data['count_utrip'] = total_data['utrip_id'].map(z1) 

Tiếp theo đó chúng ta sẽ tính số lần đi đến một city của một utrip_id

total_data['user_city'] = total_data[['utrip_id','city_id']].apply(tuple, axis=1)
z = total_data['user_city'].value_counts() 

z1 = z.to_dict() #converts to dictionary

total_data['count_u_c'] = total_data['user_city'].map(z1) 

Tính số lần đi đến hotel_country của một utrip_id

total_data['user_hotel'] = total_data[['utrip_id','hotel_country']].apply(tuple, axis=1)
z = total_data['user_hotel'].value_counts() 

z1 = z.to_dict() #converts to dictionary

total_data['count_u_h'] = total_data['user_hotel'].map(z1) 

Tính số ngày trong một chuyển đi của mỗi utrip_id

total_data['checkin'] = pd.to_datetime(total_data['checkin'])
total_data['checkin1'] = total_data['checkin'].map(datetime.datetime.toordinal)
total_data['checkout'] = pd.to_datetime(total_data['checkout'])
total_data['checkout1'] = total_data['checkout'].map(datetime.datetime.toordinal)
total_data['days'] = total_data['checkout1'] - total_data['checkin1']

Mình xem xem tổng (total_data['count_u_c'] + total_data['count_u_h'] + total_data['d']) là rating của mỗi utrip_id với một city_id để training mô hình.

total_data['count_u_c'] + total_data['count_u_h'] + total_data['d']
# data train 
data_train = total_data[['utrip_id', 'city_id', 'new_rt']]
# drop duplicate 
data_train2 =data_train[['utrip_id', 'city_id']].drop_duplicates()
data_train1 = data_train.groupby(['utrip_id', 'city_id']).max()['new_rt']
c_maxes = data_train.groupby(['utrip_id', 'city_id']).new_rt.transform(max)
data_train_1 = data_train.loc[data_train.new_rt == c_maxes]
data_train1 = data_train.sort_values("new_rt", ascending=False)
data_train1 = data_train.drop_duplicates(['utrip_id', 'city_id'])

Tiếp theo chúng ta sẽ normalize dữ liệu city_id và utrip_id về dạng theo thứ tự từ 1-> n. Đầu tiên là với utrip_id

utrip = data_train1.utrip_id.value_counts().to_frame()
utrip = utrip.reset_index()
utrip[['utrip_id_new', 'utrip_id']] =utrip[['index', 'utrip_id']]
utrip = utrip[['utrip_id_new', 'utrip_id', 'count']] 
utrip = utrip.reset_index()

Tiếp theo đến city_id

city_data = data_train1.city_id.value_counts().to_frame()
city_data = city_data.reset_index()
city_data[['city_id', 'count']] = city_data[['index', 'city_id']]
city_data = city_data[['city_id', 'count']]
city_data = city_data.reset_index()

Sau khi đã normalize xong thì chúng ta merge lại để tạo data_train nhé.

utrip_id_new = utrip[['utrip_id', 'utrip_id_new']]
city_id_new = city_data[['city_id', 'city_id_new']]
data_train1 = data_train1.merge(city_id_new, how='left')
data_train1 = data_train1.merge(utrip_id_new, how='inner')

Hình: dữ liệu data_train_1

Bây giờ chúng ta sẽ train với implicit

#install implicit
!pip install implicit
import implicit
import scipy.sparse as sparse
from scipy.sparse.linalg import spsolve

Chuyển dữ liệu về dạng sparse matrix

user_id = pd.CategoricalDtype(sorted(data_train1.utrip_id_new.unique()), ordered=True)
city_id = pd.CategoricalDtype(sorted(data_train1.city_id_new.unique()), ordered=True)
rating = list(data_train1.new_rt)
rows = data_train1.utrip_id_new.astype(user_id).cat.codes 
# Get the associated row indices
cols = data_train1.city_id_new.astype(city_id).cat.codes 
# Get the associated column indices
data_sparse = sparse.csr_matrix((rating, (cols, rows)), shape=(city_id.categories.size, user_id.categories.size))

Định nghĩa và train mô hình

model = implicit.als.AlternatingLeastSquares(factors=416, 
                                            regularization = 0.01, 
                                            iterations = 500)
                                            
 model.fit((data_sparse*15).astype('double'))
data_test = pd.read_csv('dara_test.csv')
user_items = data_sparse.T.tocsr()
test = pd.read_csv('booking_test_set.csv')
for i in range(len(data_test)):
  # print(data_test.utrip_id_new[i])
  rec = model.recommend(data_test.utrip_id_new[i], user_items)
  rec_list_mf.append(rec[0][0])

Vậy là đã train xong mô hình baseline rồi! Mọi người tham khảo code ở đây nhé.

Kết Luận

Ở bài viết này mình đã phân tích dữ liệu của cuộc thi cũng như đưa ra 2 mô hình baseline là dự đoán thành phố tiếp theo dựa trên thành phố phổ biến của từng hotel_country. Mô hình thứ 2 mình sử dụng user-user based. Bên cạnh đó các bạn có thể dùng RNN hay Attention để dự đoán cho bài toán này. Mọi người có thể tham khảo mô hình Self-attention for Recommendation của mình ở đây nhé.

Cảm ơn mọi người đã đọc bài viết của mình nhé. Mọi người nhớ upvoted cho mình nha 😄

Reference

https://www.bookingchallenge.com/

https://viblo.asia/p/exploring-du-lieu-chi-mot-dong-code-trong-python-ByEZk29xKQ0

https://drive.google.com/file/d/12OjQrLsre3iIn9kap1PF-RmxTojm0IMr/view?usp=sharing

https://github.com/benfred/implicit


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí