Hướng dẫn ab testing python example - ab thử nghiệm ví dụ python

Nội phân chính

Nội phân chính

  • Từ thiết kế thử nghiệm đến thử nghiệm giả thuyết
  • 1. Thiết kế thí nghiệm của chúng tôi
  • Xây dựng một giả thuyết
  • Chọn các biến
  • Chọn cỡ mẫu
  • 2. Thu thập và chuẩn bị dữ liệu
  • 3. Hình dung kết quả
  • 4. Kiểm tra giả thuyết
  • 5. Vẽ kết luận

  • Từ thiết kế thử nghiệm đến thử nghiệm giả thuyết
  • 1. Thiết kế thí nghiệm của chúng tôi
  • Xây dựng một giả thuyết
  • Chọn các biến
  • Chọn cỡ mẫu
  • 2. Thu thập và chuẩn bị dữ liệu
  • 3. Hình dung kết quả
  • 4. Kiểm tra giả thuyết
  • 5. Vẽ kết luận

Từ thiết kế thử nghiệm đến thử nghiệm giả thuyết

Hướng dẫn ab testing python example - ab thử nghiệm ví dụ python

1. Thiết kế thí nghiệm của chúng tôi

Xây dựng một giả thuyết

Chọn các biến

  1. Chọn cỡ mẫu
  2. 2. Thu thập và chuẩn bị dữ liệu
  3. 3. Hình dung kết quả
  4. 4. Kiểm tra giả thuyết
  5. 5. Vẽ kết luận

Hình ảnh của tác giảscenario for our study:

Trong bài viết này, chúng tôi sẽ đi qua quá trình phân tích một thí nghiệm A/B, từ việc đưa ra một giả thuyết, thử nghiệm nó và cuối cùng là giải thích kết quả. Đối với dữ liệu của chúng tôi, chúng tôi sẽ sử dụng một bộ dữ liệu từ Kaggle có chứa kết quả của thử nghiệm A/B về những gì dường như là 2 thiết kế khác nhau của một trang web (Old_Page so với New_Page). Nếu bạn muốn theo dõi cùng với mã tôi đã sử dụng, vui lòng tải xuống Jupyter Notebook tại trang GitHub của tôi.online e-commerce business. The UX designer worked really hard on a new version of the product page, with the hope that it will lead to a higher conversion rate. The product manager (PM) told you that the current conversion rate is about 13% on average throughout the year, and that the team would be happy with an increase of 2%, meaning that the new design will be considered a success if it raises the conversion rate to 15%.

Ở đây, những gì chúng tôi sẽ làm:A/B test on a subset of your user base users.

1. Thiết kế thí nghiệm của chúng tôi

Xây dựng một giả thuyết

Chọn các biến

Chọn cỡ mẫutwo-tailed test:

2. Thu thập và chuẩn bị dữ liệu

3. Hình dung kết quả

4. Kiểm tra giả thuyếtp and pₒ stand for the conversion rate of the new and old design, respectively. We’ll also set a confidence level of 95%:

5. Vẽ kết luận

Hình ảnh của tác giảα value is a threshold we set, by which we say “if the probability of observing a result as extreme or more (p-value) is lower than α, then we reject the Null hypothesis”. Since our α=0.05 (indicating 5% probability), our confidence (1 — α) is 95%.

Trong bài viết này, chúng tôi sẽ đi qua quá trình phân tích một thí nghiệm A/B, từ việc đưa ra một giả thuyết, thử nghiệm nó và cuối cùng là giải thích kết quả. Đối với dữ liệu của chúng tôi, chúng tôi sẽ sử dụng một bộ dữ liệu từ Kaggle có chứa kết quả của thử nghiệm A/B về những gì dường như là 2 thiết kế khác nhau của một trang web (Old_Page so với New_Page). Nếu bạn muốn theo dõi cùng với mã tôi đã sử dụng, vui lòng tải xuống Jupyter Notebook tại trang GitHub của tôi.

Chọn các biến

Chọn cỡ mẫutwo groups:

  • 2. Thu thập và chuẩn bị dữ liệu
  • 3. Hình dung kết quả

4. Kiểm tra giả thuyết

5. Vẽ kết luận

  • Hình ảnh của tác giả
  • Trong bài viết này, chúng tôi sẽ đi qua quá trình phân tích một thí nghiệm A/B, từ việc đưa ra một giả thuyết, thử nghiệm nó và cuối cùng là giải thích kết quả. Đối với dữ liệu của chúng tôi, chúng tôi sẽ sử dụng một bộ dữ liệu từ Kaggle có chứa kết quả của thử nghiệm A/B về những gì dường như là 2 thiết kế khác nhau của một trang web (Old_Page so với New_Page). Nếu bạn muốn theo dõi cùng với mã tôi đã sử dụng, vui lòng tải xuống Jupyter Notebook tại trang GitHub của tôi.

Ở đây, những gì chúng tôi sẽ làm:

Chọn cỡ mẫu

2. Thu thập và chuẩn bị dữ liệu

3. Hình dung kết quảthe larger the sample size, the more precise our estimates (i.e. the smaller our confidence intervals), the higher the chance to detect a difference in the two groups, if present.

Mặt khác, mẫu của chúng tôi càng lớn, nghiên cứu của chúng tôi càng đắt tiền (và không thực tế).

Vậy chúng ta nên có bao nhiêu người trong mỗi nhóm?

Cỡ mẫu chúng ta cần được ước tính thông qua một thứ gọi là phân tích năng lượng và nó phụ thuộc vào một số yếu tố:

  • Sức mạnh của thử nghiệm (1 -) - Điều này thể hiện xác suất tìm thấy sự khác biệt thống kê giữa các nhóm trong thử nghiệm của chúng tôi khi có sự khác biệt thực sự. Điều này thường được đặt ở mức 0,8 theo quy ước (ở đây, thêm thông tin về sức mạnh thống kê, nếu bạn tò mò) (1 — β) — This represents the probability of finding a statistical difference between the groups in our test when a difference is actually present. This is usually set at 0.8 by convention (here’s more info on statistical power, if you are curious)
  • Giá trị alpha (α) - Giá trị tới hạn mà chúng tôi đặt trước đó thành 0,05 (α) — The critical value we set earlier to 0.05
  • Kích thước hiệu ứng - Sự khác biệt lớn như thế nào chúng ta mong đợi sẽ có giữa tỷ lệ chuyển đổi — How big of a difference we expect there to be between the conversion rates

Vì nhóm của chúng tôi sẽ hài lòng với chênh lệch 2%, chúng tôi có thể sử dụng 13% và 15% để tính toán kích thước hiệu ứng mà chúng tôi mong đợi.

May mắn thay, Python chăm sóc tất cả các tính toán này cho chúng tôi:Python takes care of all these calculations for us:

# Packages imports
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.stats.api as sms
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from math import ceil

%matplotlib inline

# Some plot styling preferences
plt.style.use('seaborn-whitegrid')
font = {'family' : 'Helvetica',
'weight' : 'bold',
'size' : 14}

mpl.rc('font', **font)

effect_size = sms.proportion_effectsize(0.13, 0.15) # Calculating effect size based on our expected rates

required_n = sms.NormalIndPower().solve_power(
effect_size,
power=0.8,
alpha=0.05,
ratio=1
) # Calculating sample size needed

required_n = ceil(required_n) # Rounding up to next whole number

print(required_n)

df = pd.read_csv('ab_data.csv')

df.head()

8

Chúng tôi cần ít nhất 4720 quan sát cho mỗi nhóm.at least 4720 observations for each group.

Đã đặt tham số

df = pd.read_csv('ab_data.csv')

df.head()

9 thành 0,8 trong thực tế có nghĩa là nếu có sự khác biệt thực tế về tỷ lệ chuyển đổi giữa các thiết kế của chúng tôi, giả sử sự khác biệt là cái chúng tôi ước tính (13% so với 15%), chúng tôi có khoảng 80% cơ hội để phát hiện nó Như có ý nghĩa thống kê trong thử nghiệm của chúng tôi với kích thước mẫu mà chúng tôi đã tính toán.

2. Thu thập và chuẩn bị dữ liệu

Công cụ tuyệt vời! Vì vậy, bây giờ chúng tôi có cỡ mẫu cần thiết, chúng tôi cần thu thập dữ liệu. Thông thường tại thời điểm này, bạn sẽ làm việc với nhóm của mình để thiết lập thử nghiệm, có khả năng với sự trợ giúp của nhóm kỹ thuật và đảm bảo rằng bạn thu thập đủ dữ liệu dựa trên kích thước mẫu cần thiết.

Tuy nhiên, vì chúng tôi sẽ sử dụng một bộ dữ liệu mà chúng tôi tìm thấy trực tuyến, để mô phỏng tình huống này, chúng tôi sẽ:

  1. Tải xuống bộ dữ liệu từ Kaggle
  2. Đọc dữ liệu vào một khung dữ liệu gấu trúc
  3. Kiểm tra và làm sạch dữ liệu khi cần thiết
  4. Lấy mẫu ngẫu nhiên
    df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    0 hàng từ DataFrame cho mỗi nhóm *

*Lưu ý: Thông thường, chúng ta sẽ không cần phải thực hiện bước 4, đây chỉ là vì lợi ích của bài tậpNote: Normally, we would not need to perform step 4, this is just for the sake of the exercise

Vì tôi đã tải xuống bộ dữ liệu, tôi sẽ đi thẳng đến số 2.

df = pd.read_csv('ab_data.csv')

df.head()

df.info()
RangeIndex: 294478 entries, 0 to 294477
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 294478 non-null int64
1 timestamp 294478 non-null object
2 group 294478 non-null object
3 landing_page 294478 non-null object
4 converted 294478 non-null int64
dtypes: int64(2), object(3)
memory usage: 11.2+ MB
# To make sure all the control group are seeing the old page and viceversa

pd.crosstab(df['group'], df['landing_page'])

Có 294478 hàng trong DataFrame, mỗi hàng đại diện cho một phiên người dùng, cũng như 5 cột:294478 rows in the DataFrame, each representing a user session, as well as 5 columns :

  • df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    1 - ID người dùng của mỗi phiên
  • df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    2 - Dấu thời gian cho phiên
  • df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    3 - Nhóm người dùng nào được gán cho phiên đó {
    df = pd.read_csv('ab_data.csv')

    df.head()

    1,
    df = pd.read_csv('ab_data.csv')

    df.head()

    2}
  • df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    6 - Thiết kế mà mỗi người dùng đã thấy trong phiên đó {
    df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    7,
    df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    8}
  • df.info()
    RangeIndex: 294478 entries, 0 to 294477
    Data columns (total 5 columns):
    # Column Non-Null Count Dtype
    --- ------ -------------- -----
    0 user_id 294478 non-null int64
    1 timestamp 294478 non-null object
    2 group 294478 non-null object
    3 landing_page 294478 non-null object
    4 converted 294478 non-null int64
    dtypes: int64(2), object(3)
    memory usage: 11.2+ MB
    # To make sure all the control group are seeing the old page and viceversa

    pd.crosstab(df['group'], df['landing_page'])

    9 - Cho dù phiên kết thúc trong chuyển đổi hay không (nhị phân, ________ 16 = không được chuyển đổi, ________ 17 = chuyển đổi)

Chúng tôi thực sự chỉ sử dụng các cột

df.info()
RangeIndex: 294478 entries, 0 to 294477
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 294478 non-null int64
1 timestamp 294478 non-null object
2 group 294478 non-null object
3 landing_page 294478 non-null object
4 converted 294478 non-null int64
dtypes: int64(2), object(3)
memory usage: 11.2+ MB
# To make sure all the control group are seeing the old page and viceversa

pd.crosstab(df['group'], df['landing_page'])

3 và
df.info()
RangeIndex: 294478 entries, 0 to 294477
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 294478 non-null int64
1 timestamp 294478 non-null object
2 group 294478 non-null object
3 landing_page 294478 non-null object
4 converted 294478 non-null int64
dtypes: int64(2), object(3)
memory usage: 11.2+ MB
# To make sure all the control group are seeing the old page and viceversa

pd.crosstab(df['group'], df['landing_page'])

9 để phân tích.

Trước khi chúng tôi tiếp tục và lấy mẫu dữ liệu để lấy tập hợp con của chúng tôi, hãy để đảm bảo không có người dùng nào được lấy mẫu nhiều lần.

session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

4

Trên thực tế, có 3894 người dùng xuất hiện nhiều lần. Vì số này khá thấp, chúng tôi sẽ tiếp tục và loại bỏ chúng khỏi DataFrame để tránh lấy mẫu cùng một người dùng.

users_to_drop = session_counts[session_counts > 1].index

df = df[~df['user_id'].isin(users_to_drop)]
print(f'The updated dataset now has {df.shape[0]} entries')

session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

5

Lấy mẫu

Bây giờ, DataFrame của chúng tôi rất đẹp và sạch sẽ, chúng tôi có thể tiến hành và lấy mẫu các mục

df.info()
RangeIndex: 294478 entries, 0 to 294477
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 294478 non-null int64
1 timestamp 294478 non-null object
2 group 294478 non-null object
3 landing_page 294478 non-null object
4 converted 294478 non-null int64
dtypes: int64(2), object(3)
memory usage: 11.2+ MB
# To make sure all the control group are seeing the old page and viceversa

pd.crosstab(df['group'], df['landing_page'])

0 cho từng nhóm. Chúng ta có thể sử dụng phương pháp
session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

7 của Pandas để thực hiện việc này, điều này sẽ thực hiện lấy mẫu ngẫu nhiên đơn giản cho chúng tôi.

Lưu ý: Tôi đã đặt

session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

8 để kết quả có thể tái tạo nếu bạn cảm thấy muốn theo dõi trên sổ ghi chép của riêng bạn: chỉ cần sử dụng
session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

8 trong chức năng của mình và bạn nên lấy mẫu giống như tôi đã làm.
: I’ve set
session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

8 so that the results are reproducible if you feel like following on your own Notebook: just use
session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

print(f'There are {multi_users} users that appear multiple times in the dataset')

8 in your function and you should get the same sample as I did.

control_sample = df[df['group'] == 'control'].sample(n=required_n, random_state=22)
treatment_sample = df[df['group'] == 'treatment'].sample(n=required_n, random_state=22)

ab_test = pd.concat([control_sample, treatment_sample], axis=0)
ab_test.reset_index(drop=True, inplace=True)

ab_test
ab_test.info()

users_to_drop = session_counts[session_counts > 1].index

df = df[~df['user_id'].isin(users_to_drop)]
print(f'The updated dataset now has {df.shape[0]} entries')

0

ab_test['group'].value_counts()

users_to_drop = session_counts[session_counts > 1].index

df = df[~df['user_id'].isin(users_to_drop)]
print(f'The updated dataset now has {df.shape[0]} entries')

1

Tuyệt vời, có vẻ như mọi thứ diễn ra như kế hoạch, và chúng tôi đã sẵn sàng để phân tích kết quả của chúng tôi.

3. Hình dung kết quả

Điều đầu tiên chúng ta có thể làm là tính toán một số thống kê cơ bản để có ý tưởng về những mẫu của chúng ta trông như thế nào.basic statistics to get an idea of what our samples look like.

conversion_rates = ab_test.groupby('group')['converted']

std_p = lambda x: np.std(x, ddof=0) # Std. deviation of the proportion
se_p = lambda x: stats.sem(x, ddof=0) # Std. error of the proportion (std / sqrt(n))

conversion_rates = conversion_rates.agg([np.mean, std_p, se_p])
conversion_rates.columns = ['conversion_rate', 'std_deviation', 'std_error']

conversion_rates.style.format('{:.3f}')

Đánh giá bởi các số liệu thống kê ở trên, có vẻ như hai thiết kế của chúng tôi đã thực hiện rất giống nhau, với thiết kế mới của chúng tôi hoạt động tốt hơn một chút, khoảng. 12,3% so với tỷ lệ chuyển đổi 12,6%.our two designs performed very similarly, with our new design performing slightly better, approx. 12.3% vs. 12.6% conversion rate.

Vẽ sơ đồ dữ liệu sẽ làm cho những kết quả này dễ nắm bắt hơn:

plt.figure(figsize=(8,6))

sns.barplot(x=ab_test['group'], y=ab_test['converted'], ci=False)

plt.ylim(0, 0.17)
plt.title('Conversion rate by group', pad=20)
plt.xlabel('Group', labelpad=15)
plt.ylabel('Converted (proportion)', labelpad=15);

Tỷ lệ chuyển đổi cho các nhóm của chúng tôi thực sự rất gần. Cũng lưu ý rằng tỷ lệ chuyển đổi của nhóm

df = pd.read_csv('ab_data.csv')

df.head()

1 thấp hơn so với những gì chúng tôi mong đợi đã đưa ra những gì chúng tôi biết về AVG của chúng tôi. Tỷ lệ chuyển đổi (12,3% so với 13%). Điều này cho thấy rằng có một số biến thể trong kết quả khi lấy mẫu từ dân số.

Vì vậy, giá trị của nhóm

df = pd.read_csv('ab_data.csv')

df.head()

2 cao hơn. Điều này có phải là sự khác biệt có ý nghĩa thống kê?Is this difference statistically significant?

4. Kiểm tra giả thuyết

Bước cuối cùng của phân tích của chúng tôi là kiểm tra giả thuyết của chúng tôi. Vì chúng tôi có một mẫu rất lớn, chúng tôi có thể sử dụng xấp xỉ thông thường để tính toán giá trị p của chúng tôi (tức là thử nghiệm z).

Một lần nữa, Python làm cho tất cả các tính toán rất dễ dàng. Chúng ta có thể sử dụng mô-đun

users_to_drop = session_counts[session_counts > 1].index

df = df[~df['user_id'].isin(users_to_drop)]
print(f'The updated dataset now has {df.shape[0]} entries')

4 để có được khoảng giá trị p và khoảng tin cậy:

df = pd.read_csv('ab_data.csv')

df.head()

0

users_to_drop = session_counts[session_counts > 1].index

df = df[~df['user_id'].isin(users_to_drop)]
print(f'The updated dataset now has {df.shape[0]} entries')

5

5. Vẽ kết luận

Vì giá trị p của chúng tôi = 0,732 cao hơn ngưỡng α = 0,05 của chúng tôi, chúng tôi không thể từ chối giả thuyết null hₒ, điều đó có nghĩa là thiết kế mới của chúng tôi không hoạt động khác biệt đáng kể (chứ đừng nói gì đến mức cũ của chúng tôi :(p-value=0.732 is way above our α=0.05 threshold, we cannot reject the Null hypothesis Hₒ, which means that our new design did not perform significantly different (let alone better) than our old one :(

Ngoài ra, nếu chúng ta nhìn vào khoảng tin cậy cho nhóm

df = pd.read_csv('ab_data.csv')

df.head()

2 ([0.116, 0.135] hoặc 11,6-13,5%), chúng ta nhận thấy rằng:

  1. Nó bao gồm giá trị cơ bản của chúng tôi là tỷ lệ chuyển đổi 13% của chúng tôi
  2. Nó không bao gồm giá trị mục tiêu của chúng tôi là 15% (mức nâng 2% mà chúng tôi đang hướng tới)

Điều này có nghĩa là nhiều khả năng tỷ lệ chuyển đổi thực sự của thiết kế mới tương tự như đường cơ sở của chúng tôi, thay vì mục tiêu 15% mà chúng tôi đã hy vọng.Đây là bằng chứng nữa cho thấy thiết kế mới của chúng tôi không có khả năng là một sự cải thiện cho thiết kế cũ của chúng tôi, và thật không may, chúng tôi đã trở lại bảng vẽ!

Bạn có thích câu chuyện của tôi không?Làm ơn cho tôi biết!

Và xin vui lòng tải xuống Jupyter Notebook tại trang GitHub của tôi.