<jupyter_start><jupyter_text>HW2: Khám phá dữ liệu, tiền xử lý, phân tích đơn giản**Vì đây là bài tập về Pandas nên yêu cầu là không được dùng vòng lặp**Họ tên: <NAME>MSSV: 19120454 ---  Cách làm bài và nộp bài (bạn cần đọc kỹ) &9889; Bạn lưu ý là mình sẽ dùng chương trình hỗ trợ chấm bài nên bạn cần phải tuân thủ chính xác qui định mà mình đặt ra, nếu không rõ thì hỏi, chứ không nên tự tiện làm theo ý của cá nhân.**Cách làm bài**Bạn sẽ làm trực tiếp trên file notebook này. Đầu tiên, bạn điền họ tên và MSSV vào phần đầu file ở bên trên. Trong file, bạn làm bài ở những chỗ có ghi là:```python YOUR CODE HEREraise NotImplementedError()```hoặc đối với những phần code không bắt buộc thì là:```python YOUR CODE HERE (OPTION)```hoặc đối với markdown cell thì là:```markdownYOUR ANSWER HERE```Tất nhiên, khi làm thì bạn xóa dòng `raise NotImplementedError()` đi.Đối những phần yêu cầu code thì thường ở ngay phía dưới sẽ có một (hoặc một số) cell chứa các bộ test để giúp bạn biết đã code đúng hay chưa; nếu chạy cell này không có lỗi gì thì có nghĩa là qua được các bộ test. Trong một số trường hợp, các bộ test có thể sẽ không đầy đủ; nghĩa là, nếu không qua được test thì là code sai, nhưng nếu qua được test thì chưa chắc đã đúng.Trong khi làm bài, bạn có thể cho in ra màn hình, tạo thêm các cell để test. Nhưng khi nộp bài thì bạn xóa các cell mà bạn tự tạo, xóa hoặc comment các câu lệnh in ra màn hình. Bạn lưu ý không được tự tiện xóa các cell hay sửa code của Thầy (trừ những chỗ được phép sửa như đã nói ở trên).Trong khi làm bài, thường xuyên `Ctrl + S` để lưu lại bài làm của bạn, tránh mất mát thông tin.*Nên nhớ mục tiêu chính ở đây là học, học một cách chân thật. Bạn có thể thảo luận ý tưởng với bạn khác cũng như tham khảo các nguồn trên mạng, nhưng sau cùng code và bài làm phải là của bạn, dựa trên sự hiểu thật sự của bạn. Khi tham khảo các nguồn trên mạng thì bạn cần ghi rõ nguồn trong bài làm. Bạn không được tham khảo bài làm của các bạn năm trước (vì nếu làm vậy thì bao giờ bạn mới có thể tự mình suy nghĩ để giải quyết vấn đề); sau khi kết thúc môn học, bạn cũng không được đưa bài làm cho các bạn khóa sau hoặc public bài làm trên Github (vì nếu làm vậy thì sẽ ảnh hưởng tới việc học của các bạn khóa sau). Nếu bạn có thể làm theo những gì mình nói thì điểm của bạn có thể sẽ không cao nhưng bạn sẽ có được những bước tiến thật sự. Trong trường hợp bạn vi phạm những điều mình nói ở trên thì sẽ bị 0 điểm cho toàn bộ môn học.***Cách nộp bài**Khi chấm bài, đầu tiên mình sẽ chọn `Kernel` - `Restart & Run All`, để restart và chạy tất cả các cell trong notebook của bạn; do đó, trước khi nộp bài, bạn nên chạy thử `Kernel` - `Restart & Run All` để đảm bảo mọi chuyện diễn ra đúng như mong đợi.Sau đó, bạn tạo thư mục nộp bài theo cấu trúc sau:- Thư mục `MSSV` (vd, nếu bạn có MSSV là 1234567 thì bạn đặt tên thư mục là `1234567`)    - File `Lab02_KhamPhaPhanTichDuLieu.ipynb` (không cần nộp các file khác)Cuối cùng, bạn nén thư mục `MSSV` này lại và nộp ở link trên moodle. Đuôi của file nén phải là .zip (chứ không được .rar hay gì khác).Bạn lưu ý tuân thủ chính xác qui định nộp bài ở trên. ---  Import<jupyter_code>import matplotlib.pyplot as plt
import numpy as np
import pandas as pd 
# YOUR CODE HERE (OPTION) 
# Nếu cần các thư viện khác thì bạn có thể import ở đây<jupyter_output><empty_output><jupyter_text>---  Thu thập dữ liệu Dữ liệu được sử dụng trong bài tập này là dữ liệu khảo sát các lập trình viên của trang StackOverflow. Mình download dữ liệu [ở đây](https://drive.google.com/file/d/1dfGerWeWkcyQ9GX9x20rdSGj7WtEpzBB/view) và có bỏ đi một số cột để đơn giản hóa. Theo mô tả trong file "README_2020.txt" của StackOverflow:>The enclosed data set is the full, cleaned results of the 2020 Stack Overflow Developer Survey. Free response submissions and personally identifying information have been removed from the results to protect the privacy of respondents. There are three files besides this README:>>1. survey_results_public.csv - CSV file with main survey results, one respondent per row and one column per answer>2. survey_results_schema.csv - CSV file with survey schema, i.e., the questions that correspond to each column name>3. so_survey_2020.pdf - PDF file of survey instrument>>The survey was fielded from February 5 to February 28, 2020. The median time spent on the survey for qualified responses was 16.6 minutes.>>Respondents were recruited primarily through channels owned by Stack Overflow. The top 5 sources of respondents were onsite messaging, blog posts, email lists, Meta posts, banner ads, and social media posts. Since respondents were recruited in this way, highly engaged users on Stack Overflow were more likely to notice the links for the survey and click to begin it.File "survey_results_public-short.csv" mà mình đính kèm là phiên bản đơn giản hóa của file "survey_results_public.csv" (từ 61 cột, mình bỏ xuống còn 29 cột). Đây là file dữ liệu chính mà bạn sẽ làm trong bài tập này. Ngoài ra, mình còn đính kèm 2 file phụ: (1) file "survey_results_schema-short.csv" là file cho biết ý nghĩa của các cột, và (2) file "so_survey_2020.pdf" là file khảo sát gốc của StackOverflow.Để ý: - Dữ liệu này không đại diện được cho cộng đồng lập trình viên trên toàn thế giới, mà chỉ giới hạn trong tập những lập trình viên thực hiện khảo sát của StackOverflow. Những câu trả lời có được thông qua tập dữ liệu này cũng sẽ bị giới hạn trong phạm vi đó.- Dữ liệu có đúng không? Về cơ bản là ta không biết được. Ở đây, mục đích chính là học qui trình Khoa Học Dữ Liệu và các câu lệnh của Pandas nên ta sẽ **giả định** phần lớn dữ liệu là đúng và tiếp tục làm.Cũng theo file "README_2020.txt", dữ liệu này được StackOverflow public với license như sau:>This database - The Public 2020 Stack Overflow Developer Survey Results - is made available under the Open Database License (ODbL): http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/>>TLDR: You are free to share, adapt, and create derivative works from The Public 2020 Stack Overflow Developer Survey Results as long as you attribute Stack Overflow, keep the database open (if you redistribute it), and continue to share-alike any adapted database under the ODbl. ---  Khám phá dữ liệu  Đọc dữ liệu từ file (0.25đ) Đầu tiên, bạn viết code để đọc dữ liệu từ file "survey_results_public-short.csv" và lưu kết quả vào DataFrame `survey_df`; ta thống nhất là sẽ để file dữ liệu này cùng cấp với file notebook và khi đọc file thì chỉ truyền vào tên của file. Ngoài ra, bạn cũng cần cho cột `Respondent` (id của người làm khảo sát) làm cột index của `survey_df`.<jupyter_code># YOUR CODE HERE
survey_df = pd.read_csv('survey_results_public-short.csv').set_index(['Respondent'])
# TEST
survey_df.head()<jupyter_output><empty_output><jupyter_text>Dữ liệu có bao nhiêu dòng và bao nhiêu cột? (0.25đ) Kế đến, bạn tính số dòng và số cột của DataFrame `survey_df` và lần lượt lưu vào biến `num_rows` và `num_cols`.<jupyter_code># YOUR CODE HERE
(num_rows, num_cols) = survey_df.shape
# TEST
assert num_rows == 64461
assert num_cols == 28<jupyter_output><empty_output><jupyter_text>Mỗi dòng có ý nghĩa gì? Có vấn đề các dòng có ý nghĩa khác nhau không? Theo file "README_2020.txt" cũng như theo quan sát sơ bộ về dữ liệu, mỗi dòng trong DataFrame `survey_df` cho biết kết quả làm khảo sát của một người. Có vẻ không có vấn đề các dòng có ý nghĩa khác nhau.   Dữ liệu có các dòng bị lặp không? (0.25đ) Kế đến, bạn tính số dòng có index (id của người làm khảo sát) bị lặp và lưu vào biến `num_duplicated_rows`. Trong nhóm các dòng có index giống nhau thì dòng đầu tiên không tính là bị lặp.<jupyter_code># YOUR CODE HERE
num_duplicated_rows = survey_df.index.duplicated(keep='first').sum()
# TEST
assert num_duplicated_rows == 0<jupyter_output><empty_output><jupyter_text>Mỗi cột có ý nghĩa gì? (0.25đ) Để xem ý nghĩa của mỗi cột thì:- Trước tiên, bạn cần đọc file "survey_results_schema-short.csv" vào DataFrame `col_meaning_df`; bạn cũng cần cho cột "Column" làm cột index. - Sau đó, bạn chỉ cần hiển thị DataFrame `col_meaning_df` ra để xem (vụ này khó nên ở dưới mình đã làm cho bạn ở cell có dòng " TEST" 😉). Tuy nhiên, bạn sẽ thấy ở cột "QuestionText": các chuỗi mô tả bị cắt do quá dài. Do đó, trước khi hiển thị DataFrame `col_meaning_df`, bạn cũng cần chỉnh sao đó để các chuỗi mô tả không bị cắt (vụ này bạn tự search Google, gợi ý: bạn sẽ dùng đến câu lệnh `pd.set_option`).<jupyter_code># YOUR CODE HERE
col_meaning_df = pd.read_csv('survey_results_schema-short.csv').set_index(['Column'])
pd.set_option('display.max_colwidth', None)
# TEST
col_meaning_df<jupyter_output><empty_output><jupyter_text>Trước khi đi tiếp, bạn nên đọc kết quả hiển thị ở trên và đảm bảo là bạn đã hiểu ý nghĩa của các cột. Để hiểu ý nghĩa của cột, có thể bạn sẽ cần xem thêm các giá trị của cột bên DataFrame `survey_df`.   Mỗi cột hiện đang có kiểu dữ liệu gì? Có cột nào có kiểu dữ liệu chưa phù hợp để có thể xử lý tiếp không? (0.25đ) Kế đến, bạn tính kiểu dữ liệu (dtype) của mỗi cột trong DataFrame `survey_df` và lưu kết quả vào Series `dtypes` (Series này có index là tên cột).<jupyter_code># YOUR CODE HERE
dtypes = survey_df.dtypes
dtypes
# TEST
float_cols = set(dtypes[(dtypes==np.float32) | (dtypes==np.float64)].index)
assert float_cols == {'Age', 'ConvertedComp', 'WorkWeekHrs'}
object_cols = set(dtypes[dtypes == object].index)
assert len(object_cols) == 25<jupyter_output><empty_output><jupyter_text>Như bạn có thể thấy, cột "YearsCode" và "YearsCodePro" nên có kiểu dữ liệu số, nhưng hiện giờ đang có kiểu dữ liệu object. Ta hãy thử xem thêm về các giá trị 2 cột này.<jupyter_code>survey_df['YearsCode'].unique()
survey_df['YearsCodePro'].unique()<jupyter_output><empty_output><jupyter_text>Ta nên đưa 2 cột này về dạng số để có thể tiếp tục khám phá (tính min, median, max, ...). ---  Tiền xử lý (0.5đ) Bạn sẽ thực hiện tiền xử lý để chuyển 2 cột "YearsCode" và "YearsCodePro" về dạng số (float). Trong đó: "Less than 1 year" $\to$ 0, "More than 50 years" $\to$ 51. Sau khi chuyển thì `survey_df.dtypes` sẽ thay đổi.<jupyter_code># YOUR CODE HERE

# "Less than 1 year" -> 0, "More than 50 years" -> 51
survey_df['YearsCode'].replace('Less than 1 year', '0', inplace=True)
survey_df['YearsCode'].replace('More than 50 years', '51', inplace=True)
survey_df['YearsCodePro'].replace('Less than 1 year', '0', inplace=True)
survey_df['YearsCodePro'].replace('More than 50 years', '51', inplace=True)

# Object -> float
survey_df['YearsCode'] = pd.to_numeric(survey_df['YearsCode'], downcast='float')
survey_df['YearsCodePro'] = pd.to_numeric(survey_df['YearsCodePro'], downcast='float')

survey_df.dtypes
# TEST
assert survey_df['YearsCode'].dtype in [np.float32, np.float64]
assert survey_df['YearsCodePro'].dtype in [np.float32, np.float64]<jupyter_output><empty_output><jupyter_text>---  Quay lại bước khám phá dữ liệu   Với mỗi cột có kiểu dữ liệu dạng số, các giá trị được phân bố như thế nào? (1đ)(Trong đó: phần tính các mô tả của mỗi cột chiếm 0.5đ, phần tính số lượng giá trị không hợp lệ của mỗi cột chiếm 0.5đ) Với các cột có kiểu dữ liệu số, bạn sẽ tính:- Tỉ lệ % (từ 0 đến 100) các giá trị thiếu - Giá trị min- Giá trị lower quartile (phân vị 25)- Giá trị median (phân vị 50)- Giá trị upper quartile (phân vị 75)- Giá trị maxBạn sẽ lưu kết quả vào DataFrame `nume_col_info_df`, trong đó: - Tên của các cột là tên của các cột số trong `survey_df`- Tên của các dòng là: "missing_percentage", "min", "lower_quartile", "median", "upper_quartile", "max"  Để dễ nhìn, tất cả các giá trị bạn đều làm tròn với 1 chữ số thập phân bằng phương thức `.round(1)`.<jupyter_code># YOUR CODE HERE
def missing_percentage(x):
    return (x.isnull().sum() * 100 / len(x)).round(1)

def lower_quartile(x):
    return x.quantile(0.25)

def median(x):
    return x.quantile(0.50)

def upper_quartile(x):
    return x.quantile(0.75)

# Only numeric attributes
cols_name = survey_df.select_dtypes(include=np.number).columns.tolist()

# List of aggregate functions
rows_name = [missing_percentage, min, lower_quartile, median, upper_quartile, max]

# Aggregate
nume_col_info_df = survey_df[cols_name].agg(rows_name).round(1)

nume_col_info_df
# TEST
assert nume_col_info_df.shape == (6, 5)
data = nume_col_info_df.loc[['missing_percentage', 'min', 'lower_quartile', 'median', 'upper_quartile', 'max'],
                           ['Age', 'ConvertedComp', 'WorkWeekHrs', 'YearsCode', 'YearsCodePro']].values
correct_data = np.array([[     29.5,      46.1,      36.2,      10.5,      28.1],
                         [      1. ,       0. ,       1. ,       0. ,       0. ],
                         [     24. ,   24648. ,      40. ,       6. ,       3. ],
                         [     29. ,   54049. ,      40. ,      10. ,       6. ],
                         [     35. ,   95000. ,      44. ,      17. ,      12. ],
                         [    279. , 2000000. ,     475. ,      51. ,      51. ]])
assert np.array_equal(data, correct_data)<jupyter_output><empty_output><jupyter_text>**Có giá trị không hợp lệ trong mỗi cột không? (không xét giá trị thiếu)**- Cột "Age": bạn hãy tính số lượng giá trị không hợp lệ của cột "Age" (< giá trị tương ứng trong cột "YearsCode" HOẶC < giá trị tương ứng trong cột "YearsCodePro") và lưu kết quả vào biến `num_invalid_Age_vals`.- Cột "WorkWeekHrs" (số giờ làm việc trung bình một tuần): ta thấy max là 475 giờ! Trong khi đó, 7 ngày * 24 giờ = 168 giờ! Bạn hãy tính số lượng giá trị không hợp lệ của cột "WorkWeekHrs" (> 24 * 7) và lưu kết quả vào biến `num_invalid_WorkWeekHrs`.- Cột "YearsCode": bạn hãy tính số lượng giá trị không hợp lệ của cột "YearsCode" ( giá trị tương ứng trong cột "Age") và lưu kết quả vào biến `num_invalid_YearsCode`.- Cột "YearsCodePro": bạn hãy tính số lượng giá trị không hợp lệ của cột "YearsCodePro" (> giá trị tương ứng trong cột "YearsCode" HOẶC > giá trị tương ứng trong cột "Age") và lưu kết quả vào biến `num_invalid_YearsCodePro`.<jupyter_code># YOUR CODE HERE

# Invalid conditions
invalid_age_condition = np.logical_or(survey_df['Age'] < survey_df['YearsCode'] , survey_df['Age'] < survey_df['YearsCodePro'])
invalid_workweekhrs_condition = survey_df['WorkWeekHrs'] > (24 * 7)
invalid_yearscode_condition = np.logical_or(survey_df['YearsCode'] < survey_df['YearsCodePro'] , survey_df['YearsCode'] > survey_df['Age'])
invalid_yearscodepro_condition = np.logical_or(survey_df['YearsCodePro'] > survey_df['YearsCode'] , survey_df['YearsCodePro'] > survey_df['Age'])

# Boolean lists - Valid
# False == invalid, True = valid
valid_age_list = np.where(invalid_age_condition, False, True)
valid_workweekhrs_list = np.where(invalid_workweekhrs_condition, False, True)
valid_yearscode_list = np.where(invalid_yearscode_condition, False, True)
valid_yearscodepro_list = np.where(invalid_yearscodepro_condition, False, True)

# Count the invalid
num_invalid_Age_vals = np.count_nonzero(valid_age_list == False)
num_invalid_WorkWeekHrs_vals = np.count_nonzero(valid_workweekhrs_list == False)
num_invalid_YearsCode_vals = np.count_nonzero(valid_yearscode_list == False)
num_invalid_YearsCodePro_vals = np.count_nonzero(valid_yearscodepro_list == False)
# TEST
assert num_invalid_WorkWeekHrs_vals == 62
assert num_invalid_Age_vals == 16
assert num_invalid_YearsCode_vals == 499
assert num_invalid_YearsCodePro_vals == 486<jupyter_output><empty_output><jupyter_text>Do số lượng các giá trị không hợp lệ cũng khá ít nên ta có thể tiền xử lý bằng cách xóa các dòng chứa các giá trị không hợp lệ. ---  Tiền xử lý (0.5đ) Bạn sẽ thực hiện tiền xử lý để xóa đi các dòng của DataFrame `survey_df` mà chứa ít nhất là một giá trị không hợp lệ. Sau khi tiền xử lý thì `survey_df` sẽ thay đổi.<jupyter_code># YOUR CODE HERE

# Valid list
valid_list = valid_age_list * valid_workweekhrs_list * valid_yearscode_list * valid_yearscodepro_list

# Add new attributes to the dataframe
survey_df['valid'] = valid_list

# Just keep the valid rows, and drop the valid attribute recently added
survey_df = survey_df[survey_df['valid'] == True].drop(columns=['valid'])
# TEST
assert len(survey_df) == 63900<jupyter_output><empty_output><jupyter_text>---  Quay lại bước khám phá dữ liệu  Với mỗi cột có kiểu dữ liệu không phải dạng số, các giá trị được phân bố như thế nào? (1đ) Với các cột có kiểu dữ liệu không phải số, bạn sẽ tính:- Tỉ lệ % (từ 0 đến 100) các giá trị thiếu - Số lượng các giá trị (các giá trị ở đây là các giá trị khác nhau và ta không xét giá trị thiếu): với cột mà ứng với câu hỏi dạng multichoice (ví dụ, cột "DevType"), mỗi giá trị có thể chứa nhiều choice (các choice được phân tách bởi dấu chấm phẩy), và việc đếm trực tiếp các giá trị không có nhiều ý nghĩa lắm vì số lượng tổ hợp các choice là khá nhiều; một cách khác tốt hơn mà bạn sẽ làm là đếm số lượng các choice- Tỉ lệ % (từ 0 đến 100) của mỗi giá trị được sort theo tỉ lệ % giảm dần (ta không xét giá trị thiếu, tỉ lệ là tỉ lệ so với số lượng các giá trị không thiếu): bạn dùng dictionary để lưu, key là giá trị, value là tỉ lệ %; với cột mà ứng với câu hỏi dạng multichoice, cách làm tương tự như ở trênBạn sẽ lưu kết quả vào DataFrame `cate_col_info_df`, trong đó: - Tên của các cột là tên của các cột không phải số trong `survey_df`- Tên của các dòng là: "missing_percentage", "num_values", "value_percentages"  Để dễ nhìn, tất cả các giá trị bạn đều làm tròn với 1 chữ số thập phân bằng phương thức `.round(1)`.Gợi ý: có thể bạn sẽ muốn dùng [phương thức `explode`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.explode.html).<jupyter_code># Các cột ứng với câu hỏi khảo sát multichoice
multichoice_cols = ['DevType', 'Gender', 'JobFactors', 
                    'LanguageWorkedWith', 'LanguageDesireNextYear', 
                    'MiscTechWorkedWith', 'MiscTechDesireNextYear', 
                    'NEWCollabToolsWorkedWith', 'NEWCollabToolsDesireNextYear', 
                    'PlatformWorkedWith', 'PlatformDesireNextYear', 
                    'NEWStuck']
pd.set_option('display.max_colwidth', 100) # Để dễ nhìn
pd.set_option('display.max_columns', None) # Để dễ nhìn
# YOUR CODE HERE
def diff(li1, li2):
    return list(set(li1) - set(li2)) + list(set(li2) - set(li1))

def num_values(x):
    return x.str.split(';').explode().nunique()

def value_percentages(x):
    return x.str.split(';').explode().value_counts(normalize=True).apply(lambda x: x*100).round(1).to_dict()

# Only categorical attributes
cols_name = diff(survey_df.columns.tolist() , survey_df.select_dtypes(include=np.number).columns.tolist())

# List of aggregate functions
rows_name = [missing_percentage, num_values, value_percentages]

# Aggregate
cate_col_info_df = survey_df[cols_name].agg(rows_name)

cate_col_info_df
# TEST
c = cate_col_info_df['MainBranch']
assert c.loc['missing_percentage'] == 0.5
assert c.loc['num_values'] == 5
assert c.loc['value_percentages']['I am a developer by profession'] == 73.5

c = cate_col_info_df['Hobbyist']
assert c.loc['missing_percentage'] == 0.1
assert c.loc['num_values'] == 2
assert c.loc['value_percentages']['Yes'] == 78.2

c = cate_col_info_df['DevType']
assert c.loc['missing_percentage'] == 23.6
assert c.loc['num_values'] == 23
assert c.loc['value_percentages']['Academic researcher'] == 2.2

c = cate_col_info_df['PlatformWorkedWith']
assert c.loc['missing_percentage'] == 16.5
assert c.loc['num_values'] == 16
assert c.loc['value_percentages']['Docker'] == 10.6<jupyter_output><empty_output><jupyter_text>---  Đặt câu hỏi  Sau khi khám phá dữ liệu, ta đã hiểu hơn về dữ liệu. Bây giờ, ta hãy xem thử có câu hỏi nào có thể được trả lời bằng dữ liệu này.**Một câu hỏi có thể có là:** Platform nào (Windows, Linux, Docker, AWS, ...) được yêu thích nhất, platform nào được yêu thích nhì, platform nào được yêu thích ba, ...?Một platform được xem là được yêu thích nếu một người đã dùng platform này (cột "PlatformWorkedWith") và muốn tiếp tục dùng platform trong năm kế (cột "PlatformDesireNextYear").**Trả lời được câu hỏi này sẽ** phần nào giúp ta định hướng là nên tập trung học platform nào để có thể chuẩn bị cho tương lai (mình nói "phần nào" vì ở đây dữ liệu chỉ giới hạn trong phạm vi những người làm khảo sát của StackOverflow). ---  Tiền xử lý Nếu bạn thấy cần thực hiện thêm thao tác tiền xử lý để chuẩn bị dữ liệu cho bước phân tích thì bạn làm ở đây. Bước này là không bắt buộc.<jupyter_code># YOUR CODE HERE (OPTION)<jupyter_output><empty_output><jupyter_text>---  Phân tích dữ liệu (2.25đ) Bây giờ, bạn sẽ thực hiện phân tích dữ liệu để trả lời cho câu hỏi ở trên. Cụ thể các bước như sau:- Bước 1: tính Series `most_loved_platforms`, trong đó:     - Index là tên flatform (ở bước khám phá dữ liệu, bạn đã thấy có tất cả 16 platform)    - Data là tỉ lệ % (từ 0 đến 100, được làm tròn với một chữ số thập phân bằng phương thức `round(1)`) được yêu thích (được sort giảm dần) - Bước 2: từ Series `most_loved_platforms`, bạn vẽ bar chart:    - Bạn cho các bar nằm ngang (cho dễ nhìn)    - Bạn đặt tên trục hoành là "Tỉ lệ %" Code bước 1.<jupyter_code># YOUR CODE HERE

# Create platform_df with 2 columns: PlatformWorkedWith and PlatformDesireNextYear
platform_df = survey_df[['PlatformWorkedWith', 'PlatformDesireNextYear']].dropna()
platform_df.reset_index(drop=True, inplace=True)

# String -> List
platform_df = platform_df.assign(PlatformWorkedWith = platform_df['PlatformWorkedWith'].str.split(';').values)
platform_df = platform_df.assign(PlatformDesireNextYear = platform_df['PlatformDesireNextYear'].str.split(';').values)

# "Explode" both 2 columns
platform_df = platform_df.set_index(['PlatformWorkedWith']).apply(pd.Series.explode).reset_index()
platform_df = platform_df.set_index(['PlatformDesireNextYear']).apply(pd.Series.explode).reset_index()

# Boolean lists - keep platform
# True = keep platform, False = switch platform
condition_keep_platform = platform_df['PlatformWorkedWith'] == platform_df['PlatformDesireNextYear']
list_keep_platform = np.where(condition_keep_platform, True, False)

# Just keep rows have (keep_platform == True)
platform_df['keep_platform'] = list_keep_platform
platform_df = platform_df[platform_df['keep_platform'] == True].drop(columns=['keep_platform'])

# Calculate the %
most_loved_platforms = platform_df['PlatformWorkedWith'].value_counts(normalize=True).apply(lambda x: x*100).round(1).sort_values(ascending=False)

most_loved_platforms
# TEST
assert len(most_loved_platforms) == 16
assert most_loved_platforms.loc['Linux'] == 20.2
assert most_loved_platforms.loc['Windows'] == 14.6
assert most_loved_platforms.loc['Docker'] == 12.3<jupyter_output><empty_output><jupyter_text>Code bước 2.<jupyter_code># YOUR CODE HERE
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])

platforms_name = most_loved_platforms.index.tolist()
platforms_value = most_loved_platforms.values.tolist()

plt.barh(platforms_name, platforms_value)

plt.xlabel('Tỉ lệ %')
plt.ylabel('Platform')

plt.show()<jupyter_output><empty_output><jupyter_text>Bạn đã hiểu tại sao mình khuyên bạn là nên tập làm quen dần với các câu lệnh của Linux chưa 😉  ---  Đặt câu hỏi của bạn (1.5đ) Bây giờ, đến lượt bạn phải tự suy nghĩ và đưa ra câu hỏi mà có thể trả lời bằng dữ liệu. Ngoài việc đưa ra câu hỏi, bạn cũng phải giải thích để người đọc thấy nếu trả lời được câu hỏi thì sẽ có lợi ích gì. Bạn nên sáng tạo một xíu, không nên đưa ra câu hỏi cùng dạng với câu hỏi của mình ở trên.   YOUR ANSWER HERE**Câu hỏi: Liệu có mối liên hệ nào giữa trình độ giáo dục và tiền lương trong lĩnh vực IT không? Liệu bằng cấp càng "cao" thì lương có càng "cao"?**(Chúng ta chỉ nói trong phạm vi những lập trình viên đã tham gia thực hiện khảo sát của Stack Overflow năm 2020, chứ không thể kết luận cho toàn bộ lập trình viên trên toàn bộ thế giới)Lưu ý nhỏ: đồ thị bên dưới được sắp xếp theo **số lượng người thực hiện khảo sát** của từng trình độ giáo dục.Một số kết luận có được sau khi phân tích:*   3 trình độ giáo dục có đông lập trình viên nhất lần lượt là:    *  Bậc cử nhân    *  Bậc thạc sĩ    *  Học đại học, nhưng không nhận/có bằng*   Trình độ giáo dục cao hơn **chưa chắc** đã có mức lương cao hơn. Tuy nhiên chúng ta có thể để ý rằng, nhìn chung, bậc học cao hơn sẽ có nhiều lập trình viên có được mức lương cao hơn.*   Kết luận này có thể bị ảnh hưởng nhiều bởi số lượng người tham gia khảo sát ở các trình độ giáo dục khác nhau thì khác nhau. ---  Tiền xử lý để chuẩn bị dữ liệu cho bước phân tích để trả lời cho câu hỏi của bạn Phần này là không bắt buộc.<jupyter_code># YOUR CODE HERE (OPTION)<jupyter_output><empty_output><jupyter_text>---  Phân tích dữ liệu để ra câu trả lời cho câu hỏi của bạn (2đ)<jupyter_code># YOUR CODE HERE

# Create my_df with 2 columns: ConvertedComp and EdLevel
my_df = survey_df[['ConvertedComp','EdLevel']].dropna()

# Count respondents by Education Level
ed_level_index = my_df['EdLevel'].value_counts().sort_values(ascending = False).index.tolist()
ed_level_value = my_df['EdLevel'].value_counts().sort_values(ascending = False).values.tolist()

# Just to keep organised
my_df.EdLevel = my_df.EdLevel.astype("category")
my_df.EdLevel.cat.set_categories(ed_level_index, inplace=True)
my_df.sort_values(['ConvertedComp'])

# First plot
plt.barh(ed_level_index, ed_level_value)
plt.title("Number of Respondents by Education Level")
plt.xlabel("Number of Respondents")
plt.ylabel("Education Level")
plt.show()

# Second plot
plt.scatter(my_df['ConvertedComp'], my_df['EdLevel'])
plt.title("Annual Salary by Education Level")
plt.xlabel("Annual Salary (USD)")
plt.ylabel("Education Level")
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08922295978133542, 'fraction_numerical': 0.015306520890277236, 'mean_word_length': 4.3256394260761075, 'pattern_counts': {'":': 4, '<': 75, '<?xml version=': 0, '>': 93, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '147642', 'license': 'FTL', 'n_tokens_mistral': 12272, 'n_tokens_neox': 12060, 'n_words': 3560, 'path': 'CSC14119 - Introduction to Data Science/Lab 02 - Data Understanding and Analysis/Source/Lab02_KhamPhaPhanTichDuLieu.ipynb', 'repo_name': 'buiquangbao/hcmus', 'size': '25617'}