Mobiles Price Classification

Machine Learning
Classification
Published

April 21, 2023

Overview

This notebook investigates the use of machine learning models to classify a mobile phone price range using the following dataset.

Download Labeled Dataset (.csv)

Download Unlabeled Dataset (.csv)

The analysis covers these main steps:

  1. Data preprocessing and Exploratory Data Analysis (EDA)
  2. Statistical analysis for feature selection
  3. Model training
  4. Model evaluation

To achieve optimal model performance and understand feature dynamics, the experimental framework is implemented in the model training and evaluation which evaluates two distinct feature representation scenarios across four machine learning algorithms:

  1. Experimental Scenarios:
    • Full Feature Set.
    • Statistically Selected Feature Set.
  2. Machine Learning Algorithms:
    • K-Nearest Neighbors (KNN): A distance-based classifier.
    • Decision Tree: A single-tree model evaluating rule-based splits.
    • Random Forest: An ensemble bagging technique combining multiple decision trees to reduce variance and capture non-linear relationships.
    • XGBoost: A gradient boosting algorithm that sequentially corrects residual errors.

Additionally, hyperparameter tuning via GridSearchCV is systematically applied across all model configurations to determine optimal model parameters and prevent overfitting.

The primary objective is to perform an end-to-end machine learning process, feature selection, and model choice affect classification performance.

Install and Import Packages

%pip install --upgrade -q matplotlib joblib pandas scikit-learn scipy seaborn xgboost
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

Load Dataset

df = pd.read_csv('data/mobiles.csv')
df
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores ... px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi price_range
0 842 0 2.2 0 1 0 7 0.6 188 2 ... 20 756 2549 9 7 19 0 0 1 1
1 1021 1 0.5 1 0 1 53 0.7 136 3 ... 905 1988 2631 17 3 7 1 1 0 2
2 563 1 0.5 1 2 1 41 0.9 145 5 ... 1263 1716 2603 11 2 9 1 1 0 2
3 615 1 2.5 0 0 0 10 0.8 131 6 ... 1216 1786 2769 16 8 11 1 0 0 2
4 1821 1 1.2 0 13 1 44 0.6 141 2 ... 1208 1212 1411 8 2 15 1 1 0 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1995 794 1 0.5 1 0 1 2 0.8 106 6 ... 1222 1890 668 13 4 19 1 1 0 0
1996 1965 1 2.6 1 0 0 39 0.2 187 4 ... 915 1965 2032 11 10 16 1 1 1 2
1997 1911 0 0.9 1 1 1 36 0.7 108 8 ... 868 1632 3057 9 1 5 1 1 0 3
1998 1512 0 0.9 0 4 1 46 0.1 145 5 ... 336 670 869 18 10 19 1 1 1 0
1999 510 1 2.0 1 5 1 45 0.9 168 6 ... 483 754 3919 19 4 2 1 1 1 3

2000 rows × 21 columns

Data Preprocessing and EDA

First, let’s take a look at the datasets overview.

df.info()
<class 'pandas.DataFrame'>
RangeIndex: 2000 entries, 0 to 1999
Data columns (total 21 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   battery_power  2000 non-null   int64  
 1   blue           2000 non-null   int64  
 2   clock_speed    2000 non-null   float64
 3   dual_sim       2000 non-null   int64  
 4   fc             2000 non-null   int64  
 5   four_g         2000 non-null   int64  
 6   int_memory     2000 non-null   int64  
 7   m_dep          2000 non-null   float64
 8   mobile_wt      2000 non-null   int64  
 9   n_cores        2000 non-null   int64  
 10  pc             2000 non-null   int64  
 11  px_height      2000 non-null   int64  
 12  px_width       2000 non-null   int64  
 13  ram            2000 non-null   int64  
 14  sc_h           2000 non-null   int64  
 15  sc_w           2000 non-null   int64  
 16  talk_time      2000 non-null   int64  
 17  three_g        2000 non-null   int64  
 18  touch_screen   2000 non-null   int64  
 19  wifi           2000 non-null   int64  
 20  price_range    2000 non-null   int64  
dtypes: float64(2), int64(19)
memory usage: 328.3 KB

Based on the output above, here are some informations about the dataset. - The dataset has 2000 rows and 21 columns. - The target variable is ‘price_range’. - Notice that there is no feature with missing values. - All features are already in numerical form so that they don’t need an encoding for the categorical features.

df['price_range'].value_counts()
price_range
1    500
2    500
3    500
0    500
Name: count, dtype: int64
df['price_range'].value_counts().plot(kind='bar')

We can see that the dataset is balanced since it has exactly same number of data points for each class in the target variable ‘price_range’.

Checking dan Dropping Duplicated Rows

Let’s check the duplicated rows in train dataset and drop them if exist.

# check and drop duplicated rows (if exist)
total_duplicates = df.duplicated().sum()
print(f'Total duplicated rows: {total_duplicates}')
if total_duplicates:
    df = df.drop_duplicates()
    print('All duplicated rows has been removed.')
else:
    print('No duplicated rows need to be dropped.')
Total duplicated rows: 0
No duplicated rows need to be dropped.

Data Visualization and Feature Selection

print('-----Unique Values and Mode in Each Column-----\n\n')
all_columns = df.drop(columns=['price_range']).columns.tolist()
for column in all_columns:
    print(f'Unique Values in {column}:')
    print(df[column].unique().tolist())
    print()
    print(f'Mode: {df[column].mode()[0]}')
    print('='*50)
-----Unique Values and Mode in Each Column-----


Unique Values in battery_power:
[842, 1021, 563, 615, 1821, 1859, 1954, 1445, 509, 769, 1520, 1815, 803, 1866, 775, 838, 595, 1131, 682, 772, 1709, 1949, 1602, 503, 961, 519, 956, 1453, 851, 1579, 1568, 1319, 1310, 644, 725, 589, 1725, 790, 560, 1347, 1646, 1253, 1656, 1195, 1514, 1723, 1054, 578, 596, 1547, 1760, 1654, 1457, 1073, 1936, 823, 987, 1757, 1063, 1484, 799, 1156, 1720, 702, 616, 1358, 1242, 1166, 1448, 1407, 605, 1038, 797, 819, 1114, 1234, 1199, 1103, 1589, 999, 1510, 1008, 1127, 1412, 1496, 1083, 668, 1309, 1724, 1977, 885, 879, 1322, 1137, 1355, 1665, 657, 593, 1883, 1732, 543, 1939, 553, 832, 1661, 1657, 1135, 1775, 783, 617, 867, 1804, 1900, 909, 1084, 1308, 1778, 1701, 825, 1379, 1659, 826, 1564, 1957, 1571, 1414, 1790, 645, 1652, 1213, 1272, 866, 536, 523, 1753, 1218, 1537, 786, 1678, 1814, 1101, 1461, 1216, 506, 843, 742, 1692, 1485, 637, 1224, 1356, 1840, 1481, 1296, 1193, 1441, 517, 748, 1126, 1572, 1569, 1270, 1854, 625, 1577, 534, 946, 685, 947, 801, 703, 1165, 1082, 959, 1502, 1380, 1266, 1934, 1905, 1831, 1562, 1490, 1523, 640, 1526, 1989, 609, 1703, 1087, 671, 1472, 765, 1642, 1265, 664, 1277, 1395, 1539, 1049, 1827, 903, 1987, 1154, 1336, 1886, 1551, 850, 660, 1225, 1452, 1686, 1634, 1708, 1151, 1578, 1689, 1488, 1313, 1715, 1442, 528, 1133, 1718, 1330, 1799, 633, 724, 822, 1918, 1891, 1373, 862, 1273, 957, 1617, 893, 1210, 708, 835, 1450, 973, 601, 507, 564, 1559, 754, 728, 1431, 767, 1722, 1992, 1876, 1283, 1281, 1066, 730, 1053, 1611, 793, 1220, 1662, 538, 1733, 1429, 1839, 659, 1953, 1172, 612, 1563, 1191, 1893, 1995, 1517, 1162, 1595, 1329, 928, 1937, 977, 768, 1464, 1177, 1348, 1956, 1751, 1530, 1997, 1707, 894, 1163, 1648, 1170, 508, 1097, 1980, 1504, 1698, 920, 1144, 1430, 972, 662, 1867, 1730, 1882, 580, 1391, 1560, 821, 811, 1034, 618, 654, 576, 667, 869, 635, 1557, 1604, 1182, 848, 1610, 1828, 880, 1394, 1960, 809, 899, 1976, 916, 763, 1864, 1108, 1011, 1067, 1334, 1899, 930, 1058, 1187, 1874, 1482, 1809, 864, 1880, 1138, 1117, 1712, 1836, 1036, 1860, 1375, 1945, 1896, 788, 1323, 1868, 1042, 1479, 1476, 1552, 1454, 1007, 652, 1726, 1029, 1582, 1478, 1178, 707, 755, 912, 651, 1483, 914, 1456, 1503, 1681, 715, 1190, 1755, 1197, 1048, 1887, 1772, 1421, 954, 853, 1469, 902, 1631, 1608, 1991, 1349, 1844, 712, 1406, 1289, 932, 1747, 1128, 513, 1742, 1512, 1986, 965, 1583, 1653, 1433, 1845, 752, 1948, 1077, 1968, 1122, 1588, 1615, 1417, 1697, 1089, 1663, 1889, 857, 1902, 1554, 1337, 1926, 1278, 1773, 1438, 1846, 1260, 512, 581, 1872, 687, 1062, 1074, 1832, 1039, 1059, 1606, 1928, 1875, 1748, 1413, 535, 1780, 1671, 1076, 532, 777, 1217, 1185, 577, 737, 704, 525, 504, 569, 1590, 1350, 641, 1002, 1408, 726, 1544, 1230, 1519, 723, 1168, 1229, 1758, 1290, 574, 1271, 1269, 1288, 1366, 1627, 1974, 587, 934, 1853, 1164, 1232, 1813, 739, 1966, 1219, 1658, 1263, 621, 1175, 561, 1835, 1719, 1770, 1312, 1878, 1871, 922, 1975, 1212, 1958, 557, 539, 1369, 1109, 598, 1944, 1919, 501, 1620, 1227, 1359, 1914, 1645, 1231, 1397, 701, 570, 1993, 955, 924, 600, 518, 1841, 1762, 1112, 709, 1315, 1462, 571, 666, 1994, 966, 568, 1897, 1695, 1623, 1721, 890, 1677, 638, 852, 1979, 710, 1027, 1793, 675, 658, 1694, 804, 1713, 706, 1362, 1527, 1314, 705, 1403, 1486, 781, 986, 1567, 1940, 1717, 1254, 1591, 1640, 663, 1744, 1624, 1188, 817, 863, 1068, 1702, 808, 696, 1981, 614, 1807, 984, 720, 1536, 771, 1303, 1004, 1972, 1822, 1159, 1782, 1884, 798, 802, 1276, 1331, 1996, 1092, 1018, 545, 554, 1264, 1030, 1432, 558, 829, 1741, 1849, 733, 1149, 891, 1911, 550, 969, 1160, 827, 1786, 774, 1368, 1592, 1892, 915, 1848, 1501, 1614, 911, 1647, 967, 1204, 1320, 1800, 1439, 1422, 1929, 1511, 1852, 691, 807, 1113, 721, 805, 918, 1236, 1387, 839, 1593, 1788, 1628, 1965, 833, 1808, 1161, 787, 854, 1184, 1973, 510, 1907, 1489, 1843, 1286, 840, 757, 814, 994, 1550, 623, 1829, 1065, 1425, 642, 1664, 1498, 981, 1673, 603, 1332, 1064, 1444, 906, 841, 878, 1132, 1181, 1497, 1549, 1372, 1005, 989, 1983, 868, 1205, 1284, 626, 1763, 694, 816, 904, 979, 1531, 874, 1558, 582, 764, 743, 1061, 1428, 674, 1625, 907, 1584, 1382, 1745, 1754, 1596, 1660, 1861, 648, 1910, 1923, 1345, 1239, 516, 672, 892, 1130, 1597, 1046, 1035, 1261, 1688, 559, 860, 1090, 686, 818, 1777, 594, 1524, 511, 1402, 919, 1963, 1881, 683, 1633, 1371, 551, 1221, 1000, 1119, 1699, 1099, 1576, 950, 1081, 606, 502, 1632, 1696, 688, 1104, 1727, 1674, 531, 1057, 673, 718, 547, 812, 980, 643, 1925, 1522, 1959, 1043, 1095, 1540, 1342, 1189, 1449, 530, 1044, 1869, 1655, 1245, 846, 741, 1672, 514, 1396, 1542, 745, 1735, 586, 1341, 1970, 1545, 1729, 680, 1106, 831, 1528, 1447, 1344, 921, 1285, 1635, 1851, 727, 877, 1009, 1587, 865, 1930, 1268, 622, 1250, 1100, 815, 1031, 1136, 1300, 732, 1299, 1129, 1487, 1093, 1816, 627, 1895, 713, 1898, 1158, 1451, 1820, 1237, 1515, 1426, 1215, 697, 717, 1971, 1794, 1240, 1180, 1670, 1566, 1935, 913, 1317, 917, 1796, 740, 676, 1020, 896, 1824, 1174, 875, 1370, 991, 584, 1541, 1365, 1644, 871, 888, 1023, 1142, 1901, 1924, 1825, 1275, 1538, 1507, 948, 1384, 1766, 1811, 1988, 1423, 1446, 1398, 1885, 1436, 1998, 1321, 1339, 882, 796, 1012, 1318, 541, 1616, 1071, 1842, 1420, 1805, 610, 1533, 1801, 794, 1325, 1328, 1731, 1791, 856, 714, 951, 1759, 908, 820, 1333, 1352, 1600, 1823, 1581, 630, 1933, 1006, 1134, 599, 1392, 602, 1249, 1404, 1927, 1714, 1201, 1147, 776, 1045, 1638, 1150, 1806, 1834, 520, 565, 1143, 761, 1015, 1183, 881, 1186, 1575, 1343, 834, 844, 1335, 1650, 963, 1837, 1028, 970, 1070, 1908, 1776, 1410, 1280, 1110, 1211, 940, 729, 1494, 1546, 895, 1856, 1202, 1171, 964, 546, 1765, 1830, 1206, 1179, 744, 1148, 1676, 1001, 1086, 634, 1641, 962, 1862, 555, 1194, 1561, 1768, 936, 855, 1307, 1509, 1208, 1812, 770, 1416, 1003, 1967, 1146, 1630, 1125, 1470, 1826, 1521, 1603, 695, 1890, 1152, 1619, 1306, 1424, 942, 1922, 548, 1783, 1969, 1248, 1010, 990, 1982, 1607, 681, 753, 1779, 1855, 731, 926, 1938, 995, 993, 719, 1946, 579, 1072, 1492, 1251, 636, 1354, 583, 1203, 527, 873, 722, 628, 1207, 537, 1565, 1052, 837, 935, 665, 1493, 925, 958, 897, 1680, 983, 592, 689, 1685, 1792, 649, 1920, 1961, 608, 1802, 591, 1367, 1075, 684, 1361, 1259, 759, 1176, 1056, 1405, 1749, 1279, 544, 590, 1327, 1906, 1667, 1913, 1157, 1784, 1262, 1139, 1467, 858]

Mode: 618
==================================================
Unique Values in blue:
[0, 1]

Mode: 0
==================================================
Unique Values in clock_speed:
[2.2, 0.5, 2.5, 1.2, 1.7, 0.6, 2.9, 2.8, 2.1, 1.0, 0.9, 1.1, 2.6, 1.4, 1.6, 2.7, 1.3, 2.3, 2.0, 1.8, 3.0, 1.5, 1.9, 2.4, 0.8, 0.7]

Mode: 0.5
==================================================
Unique Values in dual_sim:
[0, 1]

Mode: 1
==================================================
Unique Values in fc:
[1, 0, 2, 13, 3, 4, 5, 7, 11, 12, 16, 6, 15, 8, 9, 10, 18, 17, 14, 19]

Mode: 0
==================================================
Unique Values in four_g:
[0, 1]

Mode: 1
==================================================
Unique Values in int_memory:
[7, 53, 41, 10, 44, 22, 24, 9, 33, 17, 52, 46, 13, 23, 49, 19, 39, 47, 38, 8, 57, 51, 21, 5, 60, 61, 6, 11, 50, 34, 20, 27, 42, 40, 64, 14, 63, 43, 16, 48, 12, 55, 36, 30, 45, 29, 58, 25, 3, 54, 15, 37, 31, 32, 4, 18, 2, 56, 26, 35, 59, 28, 62]

Mode: 27
==================================================
Unique Values in m_dep:
[0.6, 0.7, 0.9, 0.8, 0.1, 0.5, 1.0, 0.3, 0.4, 0.2]

Mode: 0.1
==================================================
Unique Values in mobile_wt:
[188, 136, 145, 131, 141, 164, 139, 187, 174, 93, 182, 177, 159, 198, 185, 196, 121, 101, 81, 156, 199, 114, 111, 132, 143, 96, 200, 88, 150, 107, 100, 157, 160, 119, 87, 152, 166, 110, 118, 162, 127, 109, 102, 104, 148, 180, 128, 134, 144, 168, 155, 165, 80, 138, 142, 90, 197, 172, 116, 85, 163, 178, 171, 103, 83, 140, 194, 146, 192, 106, 135, 153, 89, 82, 130, 189, 181, 99, 184, 195, 108, 133, 179, 147, 137, 190, 176, 84, 97, 124, 183, 113, 92, 95, 151, 117, 94, 173, 105, 115, 91, 112, 123, 129, 154, 191, 175, 86, 98, 125, 126, 158, 170, 161, 193, 169, 120, 149, 186, 122, 167]

Mode: 182
==================================================
Unique Values in n_cores:
[2, 3, 5, 6, 1, 8, 4, 7]

Mode: 4
==================================================
Unique Values in pc:
[2, 6, 9, 14, 7, 10, 0, 15, 1, 18, 17, 11, 16, 4, 20, 13, 3, 19, 8, 5, 12]

Mode: 10
==================================================
Unique Values in px_height:
[20, 905, 1263, 1216, 1208, 1004, 381, 512, 386, 1137, 248, 151, 607, 344, 356, 862, 984, 441, 658, 902, 1314, 974, 407, 466, 201, 291, 550, 511, 187, 1171, 1358, 413, 85, 178, 311, 1134, 429, 609, 347, 1448, 434, 1725, 685, 880, 1580, 186, 202, 27, 1025, 885, 1042, 1382, 546, 1013, 690, 667, 822, 581, 265, 127, 916, 361, 322, 753, 504, 651, 1565, 108, 459, 205, 570, 1172, 160, 4, 763, 339, 1040, 166, 1168, 990, 319, 1397, 708, 1077, 928, 516, 96, 666, 557, 1605, 1226, 769, 403, 177, 942, 1572, 476, 1233, 836, 824, 391, 320, 325, 125, 846, 1770, 293, 1633, 1198, 768, 70, 117, 1158, 1055, 635, 275, 1445, 1242, 597, 538, 59, 819, 179, 1259, 396, 713, 262, 622, 327, 522, 313, 226, 605, 692, 1386, 304, 1590, 229, 839, 507, 436, 756, 387, 1265, 1852, 875, 140, 577, 975, 510, 583, 674, 1125, 747, 1612, 116, 554, 811, 730, 660, 450, 1223, 1109, 45, 299, 506, 1045, 393, 650, 433, 1176, 218, 564, 986, 561, 65, 420, 1365, 619, 849, 611, 953, 642, 1417, 247, 710, 718, 1100, 938, 825, 286, 1273, 295, 852, 1277, 129, 701, 309, 213, 1112, 199, 55, 1230, 1052, 541, 809, 1738, 404, 759, 42, 397, 964, 233, 1022, 411, 954, 1528, 416, 638, 231, 574, 148, 1290, 632, 529, 1170, 169, 105, 793, 490, 1201, 246, 417, 1177, 78, 323, 707, 457, 936, 912, 1914, 526, 156, 865, 1552, 1097, 372, 1285, 302, 594, 303, 1858, 32, 590, 235, 630, 357, 475, 1054, 555, 1360, 142, 681, 104, 637, 410, 1830, 802, 56, 1053, 221, 1728, 63, 418, 854, 1869, 185, 170, 454, 866, 44, 1331, 741, 520, 114, 1153, 474, 39, 419, 584, 312, 19, 207, 363, 209, 159, 649, 1132, 1392, 1001, 712, 384, 119, 627, 1086, 195, 14, 745, 253, 333, 629, 1715, 513, 13, 1243, 300, 58, 1107, 134, 176, 1236, 336, 1116, 1010, 1453, 788, 1301, 463, 188, 1028, 1419, 1471, 168, 130, 481, 1411, 79, 1187, 77, 493, 1173, 634, 639, 431, 71, 111, 798, 408, 373, 1362, 743, 447, 30, 162, 346, 727, 180, 1641, 269, 250, 1186, 379, 264, 1486, 694, 579, 495, 315, 329, 548, 531, 191, 823, 841, 21, 966, 270, 781, 126, 228, 1091, 1452, 398, 449, 1184, 675, 1792, 1058, 592, 810, 533, 558, 1017, 765, 947, 754, 453, 282, 494, 281, 6, 292, 164, 631, 897, 268, 861, 331, 1064, 509, 957, 236, 980, 1789, 831, 636, 518, 725, 157, 1142, 840, 401, 395, 48, 982, 478, 1215, 683, 536, 83, 249, 803, 1596, 364, 946, 1428, 1613, 1127, 691, 1802, 751, 859, 206, 1673, 263, 97, 1213, 189, 628, 837, 1087, 251, 767, 389, 920, 1027, 271, 369, 1258, 1345, 613, 430, 1467, 120, 762, 600, 1350, 1597, 402, 52, 934, 214, 612, 1111, 163, 729, 1079, 338, 1698, 1108, 951, 1197, 473, 1294, 782, 464, 137, 492, 1250, 385, 427, 1024, 240, 886, 183, 88, 1016, 80, 569, 276, 1131, 551, 553, 1391, 981, 680, 687, 1060, 217, 1140, 832, 935, 446, 1308, 1002, 150, 1248, 437, 593, 693, 234, 1545, 1103, 91, 371, 1446, 181, 1311, 1791, 1568, 706, 871, 1074, 501, 833, 485, 182, 165, 192, 1156, 377, 709, 543, 113, 1114, 461, 1430, 471, 806, 204, 545, 112, 290, 1039, 1485, 1122, 462, 1384, 1502, 589, 82, 68, 689, 208, 1339, 722, 717, 1298, 618, 1214, 193, 22, 1464, 280, 337, 1036, 1347, 778, 482, 591, 901, 715, 172, 867, 451, 1119, 1299, 671, 1404, 856, 562, 997, 606, 445, 985, 1617, 375, 1795, 486, 757, 273, 491, 448, 1713, 438, 698, 965, 1399, 532, 8, 154, 198, 860, 109, 961, 243, 1626, 480, 123, 400, 190, 626, 1, 349, 1686, 1455, 95, 580, 297, 1047, 1150, 641, 653, 103, 517, 785, 761, 1179, 673, 472, 412, 977, 359, 224, 714, 843, 308, 519, 792, 382, 342, 118, 540, 18, 987, 535, 1092, 994, 210, 144, 1222, 1178, 75, 881, 351, 1842, 758, 1323, 1175, 647, 1102, 215, 945, 1096, 575, 352, 1920, 721, 777, 458, 256, 370, 1571, 573, 90, 138, 983, 149, 625, 521, 1530, 442, 152, 29, 87, 648, 341, 993, 43, 1262, 815, 1587, 750, 820, 171, 960, 624, 1295, 1164, 829, 1734, 76, 970, 578, 655, 1692, 358, 64, 259, 873, 858, 1405, 237, 1949, 335, 1638, 1480, 1220, 1011, 1081, 1512, 1136, 1145, 477, 547, 1194, 146, 1895, 1537, 821, 774, 284, 465, 705, 38, 51, 850, 426, 1578, 1463, 1432, 145, 847, 868, 663, 1749, 586, 409, 35, 1008, 952, 244, 716, 804, 787, 704, 991, 489, 1518, 855, 1261, 115, 1203, 818, 719, 1162, 330, 956, 444, 1003, 227, 334, 1281, 898, 46, 1191, 89, 368, 239, 654, 853, 686, 992, 614, 1105, 775, 1482, 737, 1211, 314, 527, 874, 657, 1209, 15, 813, 173, 378, 805, 1901, 1573, 360, 98, 703, 919, 879, 1287, 211, 890, 1260, 621, 328, 1307, 497, 219, 1163, 24, 468, 1272, 503, 1495, 1333, 267, 1070, 158, 184, 238, 800, 670, 81, 1043, 812, 274, 1438, 1157, 1511, 925, 1185, 1706, 1603, 1207, 423, 62, 405, 678, 1619, 864, 870, 1379, 220, 306, 1123, 796, 1021, 995, 1244, 1240, 194, 1076, 1238, 135, 5, 549, 838, 739, 3, 963, 1621, 887, 567, 496, 1245, 682, 724, 257, 17, 439, 153, 1699, 530, 755, 1406, 1826, 50, 817, 212, 366, 174, 882, 688, 1012, 294, 958, 1328, 354, 1709, 1099, 783, 122, 100, 1221, 599, 1274, 1878, 318, 1353, 86, 340, 602, 643, 1335, 1541, 1094, 272, 394, 903, 923, 362, 1135, 66, 924, 376, 1083, 659, 534, 285, 343, 289, 1703, 907, 287, 1151, 1085, 1078, 1750, 1475, 1836, 283, 539, 440, 0, 467, 1325, 1313, 552, 443, 576, 596, 10, 699, 23, 1063, 1421, 623, 1563, 203, 773, 332, 742, 1420, 1801, 1693, 31, 1098, 1765, 760, 2, 469, 1180, 155, 1032, 40, 1385, 672, 1088, 499, 571, 563, 944, 1138, 161, 500, 608, 277, 225, 1310, 930, 835, 664, 460, 1581, 9, 7, 278, 1288, 979, 913, 1252, 1128, 922, 1874, 484, 1015, 1303, 406, 124, 588, 776, 1117, 380, 603, 766, 1499, 988, 1618, 857, 73, 973, 245, 937, 1524, 1649, 254, 1790, 1304, 948, 1161, 242, 1569, 978, 1147, 910, 26, 1080, 744, 1466, 764, 53, 728, 1225, 367, 424, 84, 102, 258, 1279, 523, 296, 1364, 950, 1960, 808, 94, 1684, 1050, 1658, 67, 1257, 11, 889, 1284, 911, 927, 1041, 525, 348, 1899, 1188, 679, 1224, 223, 167, 487, 74, 931, 921, 1057, 616, 740, 542, 383, 1442, 1352, 266, 321, 1451, 1048, 470, 1217, 298, 36, 617, 786, 645, 1371, 1332, 428, 1546, 1210, 789, 390, 110, 1330, 317, 892, 388, 914, 1661, 665, 662, 661, 560, 1315, 479, 888, 652, 1652, 976, 455, 1196, 1694, 610, 1457, 241, 528, 915, 483]

Mode: 347
==================================================
Unique Values in px_width:
[756, 1988, 1716, 1786, 1212, 1654, 1018, 1149, 836, 1224, 874, 1005, 748, 1440, 563, 1864, 1850, 810, 878, 1064, 1854, 1385, 822, 788, 1245, 1434, 645, 1075, 1311, 1263, 1739, 654, 1152, 1919, 881, 1249, 815, 1307, 730, 1613, 967, 1932, 714, 1456, 1652, 1810, 1791, 774, 1433, 1832, 1383, 629, 1287, 804, 1036, 1449, 820, 713, 683, 969, 975, 547, 1353, 1570, 1618, 1858, 1781, 1225, 603, 1724, 1217, 1026, 638, 1413, 1242, 1071, 778, 1552, 1486, 1206, 1616, 1752, 1122, 1049, 1894, 741, 1118, 1203, 1402, 1924, 802, 1105, 1990, 1179, 891, 1684, 961, 1317, 984, 520, 902, 1504, 1634, 1796, 1846, 1869, 1471, 1974, 513, 1244, 1661, 973, 771, 989, 1954, 1338, 1712, 863, 1376, 786, 1965, 1732, 1398, 829, 792, 580, 1056, 1264, 1248, 669, 1686, 1539, 1042, 1926, 616, 1131, 1697, 536, 1269, 1298, 1967, 1025, 800, 1359, 1996, 1013, 590, 1925, 1989, 826, 1983, 1545, 1533, 621, 1272, 1729, 974, 1554, 1611, 1937, 1942, 845, 627, 842, 1737, 1199, 1740, 648, 1136, 1220, 879, 1065, 831, 1191, 1631, 588, 500, 1698, 1299, 898, 1381, 1464, 539, 751, 1497, 1215, 1948, 1542, 1235, 1345, 589, 1182, 1052, 1429, 873, 1478, 1460, 705, 1666, 1077, 1020, 1516, 1499, 583, 1958, 1995, 642, 1178, 1404, 1040, 1453, 1677, 517, 1560, 1226, 1200, 1647, 1308, 1615, 687, 637, 1606, 1543, 980, 1441, 1009, 1754, 860, 1108, 673, 1758, 886, 1485, 1038, 946, 1462, 1604, 1882, 1816, 765, 1980, 1559, 1928, 1529, 952, 1517, 1741, 1596, 1564, 1222, 692, 1617, 1571, 1247, 1767, 1935, 1933, 1509, 954, 662, 888, 527, 1493, 1929, 764, 1092, 1891, 1699, 1664, 763, 572, 1963, 1489, 1294, 997, 655, 1458, 1243, 519, 566, 515, 1168, 882, 776, 564, 1975, 1388, 1130, 754, 1726, 1876, 709, 557, 1914, 775, 1910, 1923, 1370, 994, 1078, 724, 1104, 1795, 1442, 1023, 1361, 652, 935, 1292, 1256, 1364, 682, 1688, 1898, 1438, 1463, 1584, 1059, 1253, 939, 986, 1029, 1280, 1565, 1331, 1922, 992, 1186, 1920, 1866, 981, 1195, 1706, 749, 1711, 681, 1510, 814, 1814, 1233, 721, 1670, 550, 699, 1138, 1903, 817, 1126, 1139, 926, 1506, 726, 1753, 794, 1161, 1350, 619, 660, 1468, 1705, 1476, 1033, 559, 1223, 956, 1797, 596, 574, 990, 1053, 858, 758, 1495, 1352, 1304, 1205, 562, 585, 1629, 1798, 545, 1657, 1341, 1058, 1744, 740, 1133, 889, 1719, 1285, 1905, 1158, 1325, 772, 1743, 1208, 1050, 1366, 899, 1884, 867, 884, 1358, 998, 862, 1785, 799, 793, 695, 728, 1940, 1259, 790, 1594, 548, 1911, 1649, 1262, 1972, 1484, 1090, 1439, 592, 1777, 870, 1106, 948, 1393, 982, 1586, 1579, 644, 1157, 750, 1472, 1337, 1663, 861, 773, 1313, 1849, 1151, 1807, 1763, 1827, 1916, 1580, 1582, 1234, 1917, 1759, 848, 1803, 915, 1354, 1143, 632, 1642, 1450, 1587, 1665, 1815, 739, 963, 1508, 1000, 544, 1627, 759, 1416, 1088, 760, 857, 1100, 676, 658, 1239, 1901, 1813, 1578, 1878, 1109, 1171, 1949, 1722, 1809, 964, 1237, 1082, 828, 1241, 1710, 1793, 1951, 1704, 1897, 809, 1306, 1771, 1536, 1987, 1022, 522, 1883, 1787, 910, 781, 1501, 1176, 684, 1838, 904, 1682, 1715, 906, 1230, 1315, 599, 1302, 727, 1096, 1173, 816, 1970, 1101, 1089, 901, 1701, 1232, 630, 1184, 1991, 510, 1511, 747, 1947, 1421, 672, 1031, 575, 1282, 1435, 1651, 516, 1703, 1048, 1873, 1465, 1477, 1748, 1159, 1630, 1293, 1162, 1747, 736, 1418, 757, 1750, 549, 818, 938, 1505, 1116, 991, 577, 1374, 1251, 1674, 1016, 1327, 501, 1621, 1532, 811, 1318, 1655, 1702, 1862, 1301, 1605, 718, 1742, 1558, 766, 1799, 1658, 1998, 1491, 650, 907, 1595, 895, 1589, 1384, 1733, 922, 1291, 1648, 723, 999, 1300, 819, 1238, 643, 1633, 735, 1339, 1445, 1424, 883, 1452, 806, 849, 1812, 947, 1895, 1123, 1409, 1675, 1930, 1518, 859, 821, 1671, 1865, 1448, 896, 605, 1330, 670, 1909, 1185, 1008, 1668, 1482, 1229, 823, 1326, 932, 1641, 1051, 1889, 1982, 893, 1163, 962, 697, 1312, 1469, 704, 671, 1583, 791, 1391, 1336, 1638, 690, 1073, 1660, 1395, 783, 1145, 1169, 1360, 1423, 1577, 1181, 1228, 1523, 1513, 812, 1081, 1406, 1001, 1500, 530, 832, 1267, 928, 1382, 1011, 1076, 688, 1403, 734, 1968, 1165, 1481, 1569, 1367, 1125, 1913, 1155, 618, 1084, 1723, 1514, 937, 1119, 1556, 1819, 1394, 1197, 1956, 614, 1334, 558, 507, 1190, 1973, 506, 1019, 674, 1420, 1377, 1538, 511, 541, 940, 1659, 983, 1219, 1735, 719, 1915, 917, 1808, 1908, 1977, 930, 1877, 1997, 1284, 661, 698, 1782, 1261, 1902, 1667, 1960, 1591, 890, 864, 1994, 1392, 1427, 1731, 875, 1348, 635, 622, 653, 570, 1356, 1553, 1985, 663, 1976, 1761, 1520, 1768, 631, 843, 1623, 1964, 1801, 1992, 571, 1746, 1055, 877, 830, 1805, 722, 646, 1309, 1079, 1087, 1626, 521, 1515, 710, 1401, 1432, 1637, 1068, 755, 1531, 656, 869, 1555, 1764, 1046, 594, 1896, 1320, 1687, 1069, 1170, 1347, 1324, 966, 846, 1389, 607, 1636, 1305, 1966, 1602, 1607, 1490, 1762, 1455, 556, 1014, 1411, 525, 1843, 797, 1043, 565, 887, 639, 1804, 1581, 912, 745, 1278, 1344, 1892, 1788, 1422, 1037, 551, 1841, 876, 620, 1012, 768, 807, 1335, 1544, 1378, 707, 1609, 1593, 1057, 1032, 1473, 1656, 1134, 1756, 711, 1899, 679, 1252, 1387, 1886, 1144, 613, 909, 798, 1676, 1147, 1738, 1362, 1461, 1062, 598, 1316, 1694, 1783, 1524, 1728, 1678, 1622, 979, 1310, 970, 1931, 1528, 1614, 1437, 1004, 1683, 784, 1066, 1400, 1766, 1662, 1773, 1274, 1454, 1210, 568, 533, 696, 640, 1496, 1769, 855, 1167, 1386, 1836, 941, 1039, 518, 865, 1156, 1227, 1893, 1273, 686, 1708, 1340, 581, 1709, 1981, 1681, 1007, 1027, 1527, 1619, 694, 1103, 1713, 1250, 850, 1132, 1957, 1492, 903, 540, 512, 1098, 1717, 1193, 1639, 1431, 1880, 925, 1363, 977, 978, 1551, 1857, 1240, 1021, 703, 675, 1945, 1802, 1010, 892, 636, 1407, 1567, 584, 1349, 1692, 1183, 866, 1645, 1790, 1373, 1775, 853, 1546, 569, 1405, 1776, 1727, 1780, 1718, 1399, 542, 1003, 647, 1575, 1196, 1950, 738, 851, 1175, 1806, 1944, 623, 1372, 1459, 1853, 844, 833, 761, 509, 1487, 1811, 700, 1646, 1288, 1028, 1550, 1483, 1952, 1503, 1375, 665, 1540, 1148, 1083, 1164, 770, 1207, 1848, 1829, 1673, 1180, 1258, 1127, 742, 1099, 1986, 1566, 503, 508, 1289, 1290, 1113, 1111, 957, 1035, 1115, 1150, 708, 712, 529, 839, 1851, 1030, 538, 732, 1415, 1643, 1211, 591, 918, 1794, 1817, 908, 1177, 1006, 1371, 1635, 1534, 1135, 528, 1430, 1839, 1760, 1343, 1831, 1414, 1904, 789, 1852, 1231, 1962, 951, 1129, 1351, 840, 678, 1608, 615, 725, 1888, 1436, 769, 1870, 1451, 1255, 872, 1275, 534, 1254, 1879, 1603, 593, 1800, 1912, 1696, 1141, 610, 1721, 1466, 1397, 582, 657, 1979, 1396, 537, 885, 651, 737, 854, 1426, 743, 1890, 1632]

Mode: 874
==================================================
Unique Values in ram:
[2549, 2631, 2603, 2769, 1411, 1067, 3220, 700, 1099, 513, 3946, 3826, 1482, 2680, 373, 568, 3554, 3752, 1835, 2337, 2819, 3283, 1433, 1037, 2583, 2782, 3763, 3286, 2373, 478, 3532, 508, 2227, 3845, 1262, 1326, 2113, 3429, 3169, 2150, 2484, 3339, 1878, 1629, 504, 1152, 3587, 2296, 1270, 3238, 2059, 2053, 3112, 1440, 2908, 2552, 905, 3963, 2056, 2910, 1457, 431, 470, 2148, 2955, 3366, 3068, 3834, 1050, 3993, 3378, 2192, 392, 3709, 590, 1814, 907, 1449, 3448, 1837, 3464, 2593, 3484, 2009, 2048, 837, 854, 3210, 2746, 2334, 2822, 1971, 1410, 349, 1418, 3616, 880, 1601, 1412, 1692, 2600, 1308, 2413, 1047, 1204, 1799, 2676, 2597, 3029, 2343, 2016, 2213, 790, 3182, 3472, 2871, 3187, 1945, 488, 1214, 2598, 3242, 3534, 3426, 2488, 3255, 2863, 1496, 3801, 666, 3799, 1366, 2962, 2399, 2338, 2700, 2235, 3825, 284, 1427, 1324, 398, 3685, 2390, 278, 3556, 2196, 2268, 2965, 2341, 3557, 1354, 2147, 505, 1394, 374, 506, 3702, 1078, 2945, 2981, 490, 536, 2317, 3704, 2126, 2050, 2462, 1260, 3770, 1886, 3323, 2060, 3926, 2177, 728, 2478, 2842, 1617, 1472, 764, 3965, 3693, 797, 1687, 2940, 1545, 2505, 2243, 3600, 3971, 2844, 1665, 3355, 1866, 1287, 1046, 1441, 690, 2504, 2677, 2984, 1362, 3629, 3559, 2571, 2107, 1022, 711, 3117, 978, 1869, 1179, 3844, 593, 588, 3836, 2669, 2958, 3031, 3388, 2377, 2458, 2766, 3054, 3791, 625, 3256, 707, 3755, 1733, 720, 3560, 2311, 3846, 1717, 3800, 1767, 1846, 829, 2986, 2394, 2169, 1181, 575, 2977, 532, 1702, 1074, 3352, 1027, 2039, 461, 527, 3271, 606, 2513, 1675, 2698, 1129, 468, 1210, 3086, 1595, 2332, 2589, 3760, 1851, 417, 294, 3576, 3945, 927, 2044, 1477, 3161, 3660, 1604, 2211, 3822, 3922, 1203, 447, 1652, 3608, 1228, 1499, 3321, 1206, 952, 3927, 311, 955, 1109, 2969, 725, 1655, 3490, 1774, 3615, 714, 2768, 3739, 610, 715, 663, 1725, 834, 3865, 3059, 651, 796, 785, 3252, 1322, 2832, 1615, 2125, 990, 1213, 2500, 2801, 3451, 1201, 3139, 1938, 3185, 2111, 2369, 3625, 1051, 1036, 2938, 2855, 2727, 2973, 2581, 2690, 916, 2563, 3359, 941, 2280, 827, 473, 1274, 364, 3699, 2927, 1470, 2616, 2915, 2686, 1243, 3264, 2261, 2993, 706, 2542, 2523, 336, 2457, 418, 3917, 1246, 2971, 2324, 3121, 3333, 3475, 2360, 2895, 3652, 3097, 2101, 3918, 3869, 732, 3684, 3969, 1414, 1892, 3131, 2532, 3215, 1529, 2403, 3419, 3648, 1974, 2806, 2239, 582, 3890, 1141, 2511, 3838, 2734, 2330, 2244, 808, 1587, 2492, 2122, 1122, 3021, 624, 3937, 1797, 1458, 3348, 1834, 2775, 1277, 457, 438, 985, 3701, 1052, 1175, 1070, 3411, 301, 489, 2756, 2612, 1724, 2190, 1900, 3915, 1043, 3817, 2362, 1107, 2982, 2042, 1343, 2610, 2335, 1614, 1138, 604, 3955, 258, 1066, 1731, 3860, 3630, 1965, 1459, 2323, 3291, 3272, 918, 3488, 3961, 463, 3644, 1713, 429, 2765, 2084, 1125, 1223, 3672, 3100, 343, 1970, 2728, 3635, 2180, 3864, 1155, 3566, 864, 869, 591, 1333, 2208, 1955, 3416, 3803, 574, 2129, 1948, 2978, 1619, 3716, 3454, 3777, 2712, 1434, 3968, 3383, 3278, 3970, 2674, 2776, 2336, 2430, 891, 3886, 1711, 2856, 2447, 814, 2951, 3653, 1891, 701, 2811, 1701, 1853, 2445, 2287, 2003, 3595, 1726, 3952, 850, 1446, 1300, 719, 988, 2606, 2912, 2339, 1028, 3173, 1667, 1897, 3269, 445, 1073, 665, 3129, 3501, 3461, 3206, 3717, 861, 2885, 2777, 1017, 980, 3309, 3607, 1257, 759, 3720, 475, 1336, 1185, 705, 3483, 1183, 3373, 565, 3153, 3421, 316, 448, 2366, 3881, 462, 1480, 1087, 2711, 2144, 1165, 2998, 2249, 752, 1409, 3072, 1305, 3597, 3132, 3856, 1524, 3673, 3293, 3035, 584, 2675, 2382, 1391, 2346, 1076, 643, 783, 3809, 2104, 1762, 3137, 1756, 3663, 2437, 1432, 1620, 961, 2608, 514, 2304, 933, 1896, 3655, 1973, 2183, 824, 1254, 3622, 1704, 1624, 2574, 950, 273, 446, 1510, 751, 3393, 3771, 2735, 2253, 452, 1419, 969, 3441, 3786, 2710, 3533, 1735, 2298, 2027, 2406, 2419, 995, 2078, 1145, 626, 1509, 3371, 297, 3508, 2385, 1301, 2167, 3914, 2312, 2297, 411, 3226, 740, 3878, 3406, 2440, 3959, 3703, 3714, 337, 3176, 1205, 2473, 1229, 2896, 520, 1378, 3254, 1803, 2501, 3762, 1796, 2630, 1380, 3487, 3015, 3204, 433, 1906, 898, 2799, 2020, 770, 305, 2953, 1671, 3772, 2870, 2577, 2246, 2405, 3377, 3619, 1184, 3713, 3798, 1026, 1086, 2236, 1303, 3048, 616, 3764, 419, 2858, 820, 3387, 3447, 1403, 2295, 2073, 3872, 302, 1519, 404, 595, 1663, 2764, 2614, 1094, 2826, 2496, 2103, 966, 2172, 3916, 3902, 2487, 363, 3233, 3076, 1513, 542, 3481, 3991, 1790, 509, 282, 3006, 1913, 3746, 3941, 3925, 424, 1284, 277, 485, 3002, 2893, 1406, 629, 3724, 3835, 2376, 3438, 2655, 3361, 3535, 2587, 2736, 676, 2941, 3336, 3796, 3654, 2934, 2889, 3315, 1993, 2509, 3165, 286, 776, 3213, 716, 3486, 3237, 1944, 1436, 3190, 1334, 3657, 2080, 1609, 3078, 1968, 3358, 1539, 942, 3669, 3612, 2351, 1285, 515, 654, 2540, 3412, 2547, 1369, 2942, 2812, 3497, 619, 1475, 1950, 3592, 3573, 1329, 3568, 1947, 1998, 3302, 3884, 1656, 911, 3892, 3897, 656, 1653, 860, 2197, 3458, 2066, 3676, 1518, 1018, 1360, 3449, 2524, 703, 1251, 1732, 1252, 2146, 1927, 3328, 1172, 3598, 1511, 3119, 3647, 3148, 2829, 3998, 999, 1808, 589, 3317, 670, 2173, 1561, 1401, 1275, 1464, 3019, 1083, 3538, 2493, 3726, 1531, 601, 2991, 3912, 309, 323, 1489, 3731, 667, 1241, 3142, 1444, 934, 3863, 3506, 348, 550, 971, 3984, 2019, 1870, 1352, 570, 546, 3624, 2944, 1209, 2752, 3868, 3087, 947, 1484, 347, 3227, 3299, 2641, 2622, 929, 1211, 511, 3034, 2438, 3744, 1377, 3520, 1699, 1095, 325, 1649, 1898, 2609, 2265, 1445, 356, 523, 3745, 1402, 2085, 587, 2030, 816, 3705, 2800, 3773, 1754, 1904, 2705, 3537, 1370, 1222, 3102, 2648, 3162, 1368, 724, 912, 2784, 793, 560, 312, 545, 1591, 893, 2514, 1032, 819, 3833, 2043, 1816, 2166, 2469, 2575, 1375, 673, 2301, 1781, 2248, 921, 391, 1719, 3262, 2693, 441, 1267, 687, 2890, 1737, 887, 2389, 3431, 3601, 2459, 3230, 3646, 2200, 3862, 3077, 621, 2372, 2857, 3143, 3197, 2262, 815, 3022, 2573, 2678, 1282, 2074, 1080, 2867, 1075, 3063, 2479, 2814, 2094, 2315, 2519, 735, 1386, 3260, 2022, 1716, 1454, 2290, 3940, 2219, 2495, 2361, 1783, 931, 1344, 1882, 2865, 757, 3469, 3300, 2719, 3012, 2836, 594, 2968, 892, 467, 345, 3033, 2110, 3105, 874, 3681, 737, 315, 2286, 2392, 1857, 3305, 959, 2635, 1936, 3637, 896, 2349, 774, 3397, 3964, 1887, 2114, 1542, 3038, 2481, 841, 3565, 1958, 2454, 3392, 262, 530, 2562, 2632, 259, 688, 2554, 3859, 571, 402, 878, 2130, 454, 2638, 1824, 876, 2706, 659, 455, 2378, 2278, 3436, 3424, 401, 681, 3212, 2036, 1642, 3465, 3900, 1105, 2064, 1142, 1060, 2381, 1339, 2560, 285, 2476, 1862, 3178, 733, 756, 1905, 2636, 2014, 3056, 465, 1108, 851, 1430, 362, 2456, 3700, 2620, 437, 3774, 1218, 696, 3407, 2086, 1356, 3571, 2132, 3209, 2096, 1273, 3623, 368, 3130, 3784, 3885, 1494, 1471, 2001, 1486, 1321, 1877, 3499, 2124, 1221, 2316, 1068, 1930, 1316, 1742, 3265, 2115, 3144, 1571, 1817, 3340, 2790, 577, 3593, 1355, 832, 436, 722, 2802, 1780, 1247, 3066, 403, 2518, 1069, 2651, 2974, 657, 3372, 1628, 3721, 763, 792, 1568, 1276, 2520, 3423, 1868, 2171, 780, 712, 1012, 318, 3948, 1161, 3707, 2926, 471, 1220, 2754, 1005, 2439, 2302, 857, 3518, 2821, 3442, 1309, 1812, 1384, 1348, 3957, 1212, 1382, 838, 378, 909, 3632, 3396, 1861, 2528, 1280, 2189, 531, 2331, 794, 2644, 1666, 2157, 1196, 2072, 1590, 2916, 726, 3577, 2627, 685, 1693, 2137, 3696, 1342, 1379, 1164, 908, 1019, 2299, 3011, 1543, 3692, 2753, 2156, 2637, 331, 3073, 422, 2293, 3958, 432, 298, 3847, 1921, 2423, 1658, 2289, 2182, 2184, 2872, 2367, 1919, 3296, 1593, 2933, 3154, 3563, 1907, 324, 1404, 2738, 2722, 2294, 1424, 1133, 555, 3785, 3675, 435, 3183, 3495, 2647, 702, 1836, 3894, 2424, 2671, 1146, 1422, 1115, 456, 2750, 2273, 2355, 2328, 558, 1345, 425, 1208, 267, 3122, 2929, 1598, 2052, 1249, 412, 263, 639, 1550, 1646, 3695, 3235, 2258, 1825, 3887, 1813, 817, 3614, 2013, 543, 3400, 2623, 1400, 3127, 2803, 3564, 2515, 3930, 3476, 2666, 1695, 3510, 2033, 1686, 862, 957, 3494, 3083, 313, 3282, 1149, 3899, 2785, 3761, 2240, 1875, 920, 361, 1540, 1077, 3610, 512, 3024, 1610, 2201, 3338, 3473, 2203, 940, 1798, 1567, 1491, 3905, 291, 1788, 3322, 1751, 1641, 2854, 3115, 3259, 1612, 2319, 1258, 3555, 524, 3541, 3437, 1637, 3064, 1393, 1633, 3284, 464, 1234, 292, 2521, 2658, 879, 1743, 3330, 562, 1659, 1338, 2992, 3249, 2725, 586, 804, 2460, 1295, 2625, 1999, 3742, 1193, 3511, 885, 1044, 2548, 3966, 2757, 1794, 3933, 3094, 256, 1365, 2049, 3241, 867, 2175, 609, 3285, 3586, 2322, 354, 2308, 1082, 3104, 2847, 2256, 1113, 3990, 984, 1622, 2967, 1167, 2715, 1734, 2259, 2921, 1244, 2090, 2131, 3362, 1053, 3124, 1672, 1552, 3202, 1302, 2359, 3314, 440, 2002, 3167, 3480, 2466, 1337, 1462, 503, 708, 2029, 308, 552, 2082, 3996, 1795, 2004, 2083, 2661, 1903, 3779, 3904, 265, 1417, 1769, 2948, 2572, 3267, 2402, 3376, 1507, 641, 1607, 3521, 881, 769, 650, 3736, 739, 686, 3943, 2633, 3911, 3297, 3141, 3351, 991, 3248, 493, 1747, 2517, 1389, 2522, 1885, 704, 953, 928, 3839, 299, 340, 3606, 799, 3756, 2787, 3208, 3585, 2282, 968, 2391, 1534, 755, 1341, 1456, 3155, 773, 1829, 3883, 1647, 3548, 2694, 2195, 2506, 3767, 1150, 2321, 3433, 872, 1503, 3430, 2470, 1396, 2668, 2452, 1871, 1112, 967, 2498, 387, 635, 2023, 2215, 3851, 495, 2358, 1744, 1832, 2313, 2216, 1850, 2592, 296, 3579, 1180, 3962, 3978, 668, 2032, 3057, 3919]

Mode: 1229
==================================================
Unique Values in sc_h:
[9, 17, 11, 16, 8, 13, 19, 5, 14, 18, 7, 10, 12, 6, 15]

Mode: 17
==================================================
Unique Values in sc_w:
[7, 3, 2, 8, 1, 10, 9, 0, 15, 13, 5, 11, 4, 12, 6, 17, 14, 16, 18]

Mode: 1
==================================================
Unique Values in talk_time:
[19, 7, 9, 11, 15, 10, 18, 5, 20, 12, 13, 2, 4, 3, 16, 6, 14, 17, 8]

Mode: 7
==================================================
Unique Values in three_g:
[0, 1]

Mode: 1
==================================================
Unique Values in touch_screen:
[0, 1]

Mode: 1
==================================================
Unique Values in wifi:
[1, 0]

Mode: 1
==================================================
Train-Test Split

Split the the data into train and test set. Here, we are using 80:20 ratio for the train and test set.

from sklearn.model_selection import train_test_split

# separate the target variable
target = 'price_range'
y = df[target].values

X_df = df.drop(columns=[target])

# split the data into train and test set
X_train_df, X_test_df, y_train, y_test = train_test_split(X_df, y, test_size=0.2, random_state=42)
print(f'X_train_df shape: {X_train_df.shape}')
print(f'X_test_df shape: {X_test_df.shape}')
print(f'y_train shape: {y_train.shape}')
print(f'y_test shape: {y_test.shape}')
X_train_df shape: (1600, 20)
X_test_df shape: (400, 20)
y_train shape: (1600,)
y_test shape: (400,)
X_train_df.head()
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores pc px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi
968 1923 0 0.5 1 7 0 46 0.5 191 1 10 767 1759 1489 10 9 3 1 1 1
240 633 1 2.2 0 0 1 49 0.1 139 8 1 529 1009 3560 11 1 16 1 1 1
819 1236 0 0.9 1 2 1 57 0.1 188 1 14 517 809 1406 14 12 20 1 0 1
692 781 0 1.1 0 2 0 38 0.4 198 5 7 304 1674 3508 13 8 5 0 0 1
420 1456 1 0.5 1 7 0 7 0.4 105 5 12 823 1104 1587 6 5 20 1 0 1
X_test_df.head()
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores pc px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi
1860 1646 0 2.5 0 3 1 25 0.6 200 2 5 211 1608 686 8 6 11 1 1 0
353 1182 0 0.5 0 7 1 8 0.5 138 8 16 275 986 2563 19 17 19 1 0 0
1333 1972 0 2.9 0 9 0 14 0.4 196 7 18 293 952 1316 8 1 8 1 1 0
905 989 1 2.0 0 4 0 17 0.2 166 3 19 256 1394 3892 18 7 19 1 1 0
1289 615 1 0.5 1 7 0 58 0.5 130 5 8 1021 1958 1906 14 5 5 1 0 0
y_train
array([1, 2, 0, ..., 2, 3, 1], shape=(1600,))
y_test
array([0, 2, 1, 3, 1, 1, 2, 0, 3, 1, 0, 0, 2, 3, 3, 2, 3, 3, 1, 0, 0, 2,
       1, 2, 0, 1, 3, 2, 2, 0, 0, 0, 3, 0, 1, 1, 2, 0, 3, 0, 2, 3, 2, 0,
       2, 3, 2, 1, 3, 1, 3, 1, 0, 0, 1, 0, 1, 3, 0, 0, 1, 3, 3, 1, 0, 0,
       3, 3, 1, 2, 2, 2, 0, 1, 2, 0, 1, 3, 2, 2, 3, 2, 1, 0, 1, 3, 1, 3,
       3, 0, 3, 3, 2, 1, 3, 2, 2, 3, 1, 1, 0, 0, 1, 0, 0, 3, 2, 0, 1, 1,
       0, 0, 3, 1, 3, 2, 3, 2, 0, 2, 1, 3, 2, 1, 3, 3, 0, 2, 0, 2, 3, 0,
       2, 2, 0, 3, 1, 0, 0, 2, 2, 1, 3, 2, 0, 0, 0, 1, 1, 2, 3, 1, 1, 0,
       2, 2, 0, 1, 0, 2, 2, 3, 3, 2, 1, 0, 1, 2, 2, 3, 3, 0, 1, 0, 3, 1,
       1, 2, 1, 0, 0, 0, 0, 0, 3, 2, 0, 3, 0, 0, 0, 0, 1, 3, 3, 1, 0, 1,
       1, 1, 1, 2, 2, 3, 3, 3, 1, 2, 0, 0, 0, 2, 1, 1, 3, 1, 0, 2, 1, 1,
       3, 2, 3, 0, 0, 2, 1, 3, 0, 1, 2, 0, 2, 3, 2, 0, 1, 3, 3, 0, 1, 3,
       2, 3, 0, 3, 1, 2, 3, 3, 2, 1, 0, 3, 3, 1, 3, 3, 3, 3, 3, 0, 1, 2,
       2, 2, 3, 0, 2, 3, 2, 2, 2, 1, 0, 2, 0, 3, 3, 1, 3, 1, 0, 3, 1, 2,
       0, 0, 3, 0, 1, 2, 3, 3, 3, 1, 1, 0, 1, 3, 3, 0, 1, 2, 2, 0, 3, 3,
       2, 3, 2, 3, 2, 0, 2, 1, 1, 1, 0, 0, 0, 3, 3, 3, 1, 0, 1, 0, 2, 2,
       3, 0, 3, 3, 2, 1, 3, 0, 0, 2, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 2, 0,
       0, 3, 3, 0, 3, 0, 0, 2, 0, 1, 2, 2, 2, 3, 0, 3, 2, 3, 3, 3, 3, 2,
       1, 1, 0, 3, 1, 3, 3, 0, 2, 3, 2, 3, 3, 3, 0, 0, 2, 3, 0, 0, 2, 3,
       2, 1, 1, 2])

For the next exploratory data analysis, it is conducted exclusively on the Training Set. The Test Set remains completely untouched and unseen, acting as pure out-of-sample data to ensure an unbiased evaluation of the final model. Then, divide the training dataset into 2 parts: numerical features only and categorical features only.

# divide the dataframe into 2 parts: numerical features only and categorical features only
cat_columns = ['blue', 'dual_sim', 'four_g', 'three_g', 'touch_screen', 'wifi']
num_columns = list(set(X_df.columns) - set(cat_columns))

X_train_cat_df = X_train_df[cat_columns]
X_train_num_df = X_train_df[num_columns]

print(f'X_train_cat_df shape: {X_train_cat_df.shape}')
print(f'X_train_num_df shape: {X_train_num_df.shape}')
X_train_cat_df shape: (1600, 6)
X_train_num_df shape: (1600, 14)

Categorical Features

Let’s create grouped bar charts to visualize the categorical features.

countplot_df = X_train_cat_df.copy()
countplot_df[target] = y_train

n_columns = 3
n_rows = (len(num_columns) + n_columns - 1) // n_columns

fig, axes = plt.subplots(n_rows, n_columns, figsize=(15, 5 * n_rows))
axes = axes.flatten()

for i, column in enumerate(cat_columns):
    sns.countplot(data=countplot_df, x=target, hue=column, ax=axes[i])

for j in range(i + 1, len(axes)):
    fig.delaxes(axes[j])

plt.show()

This side-by-side bar symmetry demonstrates that the presence or absence of these hardware features occurs with equal probability regardless of the smartphone’s price level. The visual lack of skewness or concentration reinforces that these binary features are statistically independent of the target, providing further empirical justification for pruning them during the Chi-Square feature selection process.

To determine which categorical features hold significant predictive power over the categorical target variable (price_range), Chi-Square Test of Independence is employed.

The Chi-Square Test of Independence evaluates whether a significant association exists between two categorical variables by comparing the Observed Frequencies (\(O\)) in the sample data against the Expected Frequencies (\(E\)).

How Expected Frequencies Measure Independence

In probability theory, if two events \(A\) and \(B\) are statistically independent (i.e., having no association), the joint probability of both events occurring simultaneously is the product of their individual probabilities:

\[P(A \cap B) = P(A) \times P(B)\]

In the context of contingency tables, the Expected Frequency (\(E_{i,j}\)) for a specific cell—representing the count we would mathematically expect if the feature and the target class were completely unrelated—is computed as:

\[E_{i,j} = \frac{\text{Row Total}_i \times \text{Column Total}_j}{\text{Grand Total (N)}}\]

Measuring Association via Deviation The Chi-Square statistic quantifies the total normalized deviation between reality (\(O\)) and the assumption of independence (\(E\)):

\[\chi^2 = \sum \frac{(O_{i,j} - E_{i,j})^2}{E_{i,j}}\]

  • Small \(\chi^2\) value (\(p \ge 0.05\)): The observed distribution aligns closely with the expected distribution under independence. This indicates that knowing the categorical feature provides no statistically significant information about the target variable (\(\text{price\_range}\)). Thus, the feature is identified as noise and candidate for removal.
  • Large \(\chi^2\) value (\(p < 0.05\)): The observed frequencies diverge significantly from the independence baseline. This confirms a statistically significant association with the target, justifying the retention of the feature for machine learning modeling.
from scipy.stats import chi2_contingency

chi2_df = X_train_cat_df.copy()
chi2_df[target] = y_train

chi2_results = []

print('----- Running Chi-Square Independence Test -----\n')

for column in cat_columns:
    contingency_table = pd.crosstab(chi2_df[column], chi2_df[target])
    
    chi2_stat, p_val, dof, expected = chi2_contingency(contingency_table)

    chi2_results.append({'Feature': column,
                         'Chi-Square Stat': chi2_stat,
                         'P-Value': p_val})
    
chi2_summary_df = pd.DataFrame(chi2_results).sort_values(by='Chi-Square Stat', ascending=False).reset_index(drop=True)
print('Results:\n')
print(chi2_summary_df)

# select features with p-value < 0.05
selected_cat = chi2_summary_df[chi2_summary_df['P-Value'] < 0.05]['Feature'].tolist()
print(f'\nSelected Categorical Features: {selected_cat}\n')
print(f'Number of Selected Categorical Features: {len(selected_cat)} Out of {len(cat_columns)} Categorical Features.')
----- Running Chi-Square Independence Test -----

Results:

        Feature  Chi-Square Stat   P-Value
0  touch_screen         3.733835  0.291677
1          blue         2.447575  0.484840
2        four_g         2.387007  0.496058
3       three_g         2.100655  0.551780
4      dual_sim         1.476924  0.687606
5          wifi         1.403940  0.704611

Selected Categorical Features: []

Number of Selected Categorical Features: 0 Out of 6 Categorical Features.

A comprehensive evaluation of the binary/categorical features (blue, dual_sim, four_g, three_g, touch_screen, and wifi) indicates that these attributes exert virtually no meaningful impact on determining a smartphone’s price range.

  1. Visual Evidence: Across the grouped bar charts, any of the four target price ranges (\(\text{price\_range} \in \{0, 1, 2, 3\}\)) hover uniformly around 25% for both binary states (\(0\) and \(1\)). The lack of structural deviation or concentration in specific cells visually confirms that these features are distributed at random relative to the price categories.

  2. Statistical Confirmation (Chi-Square Test of Independence): This visual independence is rigorously backed by the Chi-Square test of independence. The calculated \(\chi^2\) values for all categorical features are small, yielding \(p\text{-values} > 0.05\). Consequently, we fail to reject the null hypothesis (\(H_0\)), confirming that these binary hardware specifications share no statistically significant association with price_range. Retaining them would only introduce irrelevant noise into distance-based estimators like KNN.

Numerical Features

Let’s also explore which numerical features have a strong relationship with the target variable by creating box plots.

boxplot_df = X_train_num_df.copy()
boxplot_df[target] = y_train

n_columns = 3
n_rows = (len(num_columns) + n_columns - 1) // n_columns

fig, axes = plt.subplots(n_rows, n_columns, figsize=(15, 5 * n_rows))
axes = axes.flatten()

for i, column in enumerate(num_columns):
    sns.boxplot(boxplot_df, x=target, y=column, ax=axes[i])

for j in range(i + 1, len(axes)):
    fig.delaxes(axes[j])

plt.show()

The boxplot visualization for ram demonstrates a distinct, non-overlapping ascending stair-step pattern across the target classes. The median values and interquartile ranges (IQR) for each price_range shift dramatically higher with each successive tier: - Price Range 0 (Low): Characterized by low RAM capacity (predominantly \(< 1,000 \text{ MB}\)). - Price Range 3 (Very High): Dominated exclusively by high RAM capacity (approaching \(3,000 - 4,000 \text{ MB}\)).

The absence of overlapping medians confirms that RAM alone provides near-perfect linear separability among the four price classes.

To determine which continuous numerical features hold significant predictive power over the categorical target variable (price_range), an Analysis of Variance (ANOVA) F-test is employed. Mathematically, ANOVA evaluates whether the mean of each continuous numerical features differs significantly across all ofthe classes in the target variable. The statistical hypotheses for this evaluation are formulated as follows:

  • Null Hypothesis (\(H_0\)): The mean of a continuous numerical features is identical across all classes in the target variable.

  • Alternative Hypothesis (\(H_1\)): At least one class’ mean is significantly different from the others.

A stringent significance threshold of \(\alpha = 0.05\) is applied. Categorical features that yield a \(p\text{-value} < 0.05\) allow us to rigorously reject the null hypothesis, confirming that the variance between the category means is statistically significant and not due to random chance. These verified features are selected for downstream dummy encoding and modeling, while non-significant features are discarded to keep the model parsimonious.

How Simple Comparison of Group Means Can Dictate The Relationship between Numerical and Categorical Data

If we discover that the mean of a numerical feature in price_range 0 is structurally different from price_range 1, price_range 2, and price_range 3, it demonstrates that knowing the value of that numerical feature provides immediate information about the target’s class. Mathematically, this implies that the variance between the groups is much larger than the variance within the groups.

from scipy import stats

anova_df = X_train_num_df.copy()
anova_df[target] = y_train

anova_results = []

print('----- Running ANOVA Test -----\n')

for column in num_columns:

    unique_cats = anova_df[target].unique()
    
    grouped_data = [anova_df[anova_df[target] == cat][column].values for cat in unique_cats]

    f_stat, p_val = stats.f_oneway(*grouped_data)
    
    anova_results.append({
        'Feature': column,
        'F-Statistic': f_stat,
        'P-Value': p_val
    })

# convert to DataFrame and sort by F_Statistics in descending order
anova_summary_df = pd.DataFrame(anova_results).sort_values(by='F-Statistic', ascending=False).reset_index(drop=True)

print('Results:\n')
print(anova_summary_df)

# select features with p-value < 0.05
selected_num = anova_summary_df[anova_summary_df['P-Value'] < 0.05]['Feature'].tolist()
print(f'\nSelected Numerical Features: {selected_num}\n')
print(f'Number of Selected Numerical Features: {len(selected_num)} Out of {len(num_columns)} Numerical Features.')
----- Running ANOVA Test -----

Results:

          Feature  F-Statistic       P-Value
0             ram  2760.069393  0.000000e+00
1   battery_power    23.083657  1.255203e-14
2        px_width    16.183182  2.302349e-10
3       px_height    14.065180  4.736223e-09
4      int_memory     4.981509  1.922441e-03
5       mobile_wt     3.197662  2.262644e-02
6            sc_h     2.072448  1.019529e-01
7         n_cores     1.595945  1.884610e-01
8           m_dep     1.574918  1.935476e-01
9       talk_time     1.500694  2.125445e-01
10             fc     1.061214  3.644836e-01
11    clock_speed     1.037116  3.750794e-01
12           sc_w     0.988743  3.971462e-01
13             pc     0.780857  5.046006e-01

Selected Numerical Features: ['ram', 'battery_power', 'px_width', 'px_height', 'int_memory', 'mobile_wt']

Number of Selected Numerical Features: 6 Out of 14 Numerical Features.

The One-Way ANOVA test yields an exceptionally massive F-statistic for ram alongside an empirical \(p\text{-value} \approx 0.0\) (far below the standard \(\alpha = 0.05\) threshold). This indicates that the variance between price class means for RAM is extraordinarily large compared to the variance within each class, formally establishing ram as the single most statistically significant feature in the dataset.

Numerical Features Standardization

As observed in the plots for numerical features, the numerical features exhibit vastly different scales and measurement ranges. One of the models that will be used here is K-Nearest Neighbor which is highly sensitive to these scale discrepancies because the optimization algorithm might inadvertently penalize or over-allocate weight to features with larger absolute magnitudes.

To mitigate this, StandardScaler is applied to transform all numerical features to possess a mean of 0 and a standard deviation of 1 (\(\mu = 0, \sigma = 1\)).

from sklearn.preprocessing import StandardScaler

X_test_num_df = X_test_df[num_columns]

scaler = StandardScaler()
X_train_num = X_train_num_df.values
X_test_num = X_test_num_df.values

X_train_num_scaled = scaler.fit_transform(X_train_num)
X_test_num_scaled = scaler.transform(X_test_num)

X_train_num_scaled_df = pd.DataFrame(data=X_train_num_scaled, columns=num_columns)
X_test_num_scaled_df = pd.DataFrame(data=X_test_num_scaled, columns=num_columns)

print(f'X_train_num_scaled_df shape: {X_train_num_scaled_df.shape}')
print(f'X_test_num_scaled_df shape: {X_test_num_scaled_df.shape}')
X_train_num_scaled_df shape: (1600, 14)
X_test_num_scaled_df shape: (400, 14)
X_train_num_scaled_df.head()
m_dep fc battery_power n_cores mobile_wt ram sc_h px_height sc_w int_memory px_width clock_speed pc talk_time
0 -0.008935 0.620111 1.548360 -1.547446 1.425710 -0.580476 -0.528061 0.275712 0.759509 0.754832 1.181997 -1.236230 0.020269 -1.445123
1 -1.403674 -0.993561 -1.379535 1.510316 -0.046246 1.336453 -0.290196 -0.258762 -1.084869 0.919763 -0.556547 0.837112 -1.476497 0.915929
2 -1.403674 -0.532512 -0.010914 -1.547446 1.340790 -0.657302 0.423400 -0.285711 1.451150 1.359577 -1.020159 -0.748385 0.685498 1.642407
3 -0.357620 -0.532512 -1.043621 0.199847 1.623858 1.288322 0.185535 -0.764042 0.528962 0.315017 0.984962 -0.504462 -0.478653 -1.081884
4 -0.357620 0.620111 0.488416 0.199847 -1.008680 -0.489767 -1.479523 0.401470 -0.162680 -1.389265 -0.336332 -1.236230 0.352883 1.642407
X_test_num_scaled_df.head()
m_dep fc battery_power n_cores mobile_wt ram sc_h px_height sc_w int_memory px_width clock_speed pc talk_time
0 0.339750 -0.301987 0.919657 -1.110623 1.680472 -1.323738 -1.003792 -0.972891 0.067867 -0.399682 0.831970 1.202995 -0.811268 0.007832
1 -0.008935 0.620111 -0.133477 1.510316 -0.074553 0.413624 1.612728 -0.829167 2.603886 -1.334288 -0.609863 -1.236230 1.018112 1.460787
2 -0.357620 1.081160 1.659574 1.073493 1.567245 -0.740606 -1.003792 -0.788745 -1.084869 -1.004427 -0.688677 1.690840 1.350727 -0.537026
3 -1.054989 -0.071463 -0.571527 -0.673800 0.718039 1.643754 1.374862 -0.871835 0.298414 -0.839496 0.335906 0.593189 1.517034 1.460787
4 -0.008935 0.620111 -1.420389 0.199847 -0.301008 -0.194499 0.423400 0.846117 -0.162680 1.414554 1.643291 -1.236230 -0.312346 -1.081884
# full and unscaled: X_train_df, X_test_df

# full and scaled
X_train_full_scaled_df = pd.concat([X_train_cat_df.reset_index(drop=True), X_train_num_scaled_df.reset_index(drop=True)], axis=1).sort_index(axis=1)
X_test_full_scaled_df = pd.concat([X_test_df[cat_columns].reset_index(drop=True), X_test_num_scaled_df.reset_index(drop=True)], axis=1).sort_index(axis=1)

# selected and unscaled
selected_columns = selected_num + selected_cat
X_train_selected_df = X_train_df[selected_columns].sort_index(axis=1)
X_test_selected_df = X_test_df[selected_columns].sort_index(axis=1)

# selected and scaled
X_train_selected_scaled_df = X_train_full_scaled_df[selected_columns].sort_index(axis=1)
X_test_selected_scaled_df = X_test_full_scaled_df[selected_columns].sort_index(axis=1)

print(f'X_train_full_scaled_df shape: {X_train_full_scaled_df.shape}')
print(f'X_test_full_scaled_df shape: {X_test_full_scaled_df.shape}')
print(f'X_train_selected_df shape: {X_train_selected_df.shape}')
print(f'X_test_selected_df shape: {X_test_selected_df.shape}')
print(f'X_train_selected_scaled_df shape: {X_train_selected_scaled_df.shape}')
print(f'X_test_selected_scaled_df shape: {X_test_selected_scaled_df.shape}')
X_train_full_scaled_df shape: (1600, 20)
X_test_full_scaled_df shape: (400, 20)
X_train_selected_df shape: (1600, 6)
X_test_selected_df shape: (400, 6)
X_train_selected_scaled_df shape: (1600, 6)
X_test_selected_scaled_df shape: (400, 6)
X_train_full_scaled_df.head()
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores pc px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi
0 1.548360 0 -1.236230 1 0.620111 0 0.754832 -0.008935 1.425710 -1.547446 0.020269 0.275712 1.181997 -0.580476 -0.528061 0.759509 -1.445123 1 1 1
1 -1.379535 1 0.837112 0 -0.993561 1 0.919763 -1.403674 -0.046246 1.510316 -1.476497 -0.258762 -0.556547 1.336453 -0.290196 -1.084869 0.915929 1 1 1
2 -0.010914 0 -0.748385 1 -0.532512 1 1.359577 -1.403674 1.340790 -1.547446 0.685498 -0.285711 -1.020159 -0.657302 0.423400 1.451150 1.642407 1 0 1
3 -1.043621 0 -0.504462 0 -0.532512 0 0.315017 -0.357620 1.623858 0.199847 -0.478653 -0.764042 0.984962 1.288322 0.185535 0.528962 -1.081884 0 0 1
4 0.488416 1 -1.236230 1 0.620111 0 -1.389265 -0.357620 -1.008680 0.199847 0.352883 0.401470 -0.336332 -0.489767 -1.479523 -0.162680 1.642407 1 0 1
X_train_selected_df.head()
battery_power int_memory mobile_wt px_height px_width ram
968 1923 46 191 767 1759 1489
240 633 49 139 529 1009 3560
819 1236 57 188 517 809 1406
692 781 38 198 304 1674 3508
420 1456 7 105 823 1104 1587
X_train_selected_scaled_df.head()
battery_power int_memory mobile_wt px_height px_width ram
0 1.548360 0.754832 1.425710 0.275712 1.181997 -0.580476
1 -1.379535 0.919763 -0.046246 -0.258762 -0.556547 1.336453
2 -0.010914 1.359577 1.340790 -0.285711 -1.020159 -0.657302
3 -1.043621 0.315017 1.623858 -0.764042 0.984962 1.288322
4 0.488416 -1.389265 -1.008680 0.401470 -0.336332 -0.489767

Run The Experiment: Train and Evaluate Models Using Hyperparameter Tuning

from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.model_selection import KFold, GridSearchCV


experiments = [
    # knn experiments
    {
        'name': 'KNN_Full_Features',
        'model': KNeighborsClassifier(),
        'use_selected_features': False,
        'needs_scaling': True,
        'params': {
            'n_neighbors': [3, 5, 7, 9, 11, 13, 15, 17, 19, 21],
        }
    },
    {
        'name': 'KNN_Feature_Selection',
        'model': KNeighborsClassifier(),
        'use_selected_features': True,
        'needs_scaling': True,
        'params': {
            'n_neighbors': [3, 5, 7, 9, 11, 13, 15, 17, 19, 21],
        }
    },
    
    # decision tree Experiments
    {
        'name': 'DecisionTree_Full_Features',
        'model': DecisionTreeClassifier(random_state=42),
        'use_selected_features': False,
        'needs_scaling': False,
        'params': {
            'criterion': ['gini', 'entropy'],
            'max_depth': [None, 3, 5, 7, 10]
        }
    },
    {
        'name': 'DecisionTree_Feature_Selection',
        'model': DecisionTreeClassifier(random_state=42),
        'use_selected_features': True,
        'needs_scaling': False,
        'params': {
            'criterion': ['gini', 'entropy'],
            'max_depth': [None, 3, 5, 7, 10]
        }
    },

    # random forest experiments
    {
        'name': 'RandomForest_Full_Features',
        'model': RandomForestClassifier(random_state=42),
        'use_selected_features': False,
        'needs_scaling': False,
        'params': {
            'criterion': ['gini', 'entropy'],
            'max_depth': [None, 3, 5, 7, 10],
            'n_estimators': [100, 200, 300, 400, 500]
        }
    },
    {
        'name': 'RandomForest_Feature_Selection',
        'model': RandomForestClassifier(random_state=42),
        'use_selected_features': True,
        'needs_scaling': False,
        'params': {
            'criterion': ['gini', 'entropy'],
            'max_depth': [None, 3, 5, 7, 10],
            'n_estimators': [100, 200, 300, 400, 500]
        }
    },

    # xgboost experiments
    {
        'name': 'XGBoost_Full_Features',
        'model': XGBClassifier(objective='multi:softprob', num_class=4, random_state=42),
        'use_selected_features': False,
        'needs_scaling': False,
        'params': {
            'n_estimators': [50, 100, 200],
            'learning_rate': [0.01, 0.05, 0.1, 0.2],
            'max_depth': [3, 5, 7]
        }
    },
    {
        'name': 'XGBoost_Feature_Selection',
        'model': XGBClassifier(objective='multi:softprob', num_class=4, random_state=42),
        'use_selected_features': True,
        'needs_scaling': False,
        'params': {
            'n_estimators': [50, 100, 200],
            'learning_rate': [0.01, 0.05, 0.1, 0.2],
            'max_depth': [3, 5, 7]
        }
    }
]

results_list = []

for exp in experiments:
    print(f'Running Experiment: {exp['name']}')

    kf = KFold(n_splits=5, shuffle=True, random_state=42)

    grid_cv = GridSearchCV(
        estimator=exp['model'],
        param_grid=exp['params'],
        cv=kf,
        scoring='accuracy',
        n_jobs=-1
    )

    if exp['use_selected_features']:
        if exp['needs_scaling']:
            X_train =  X_train_selected_scaled_df.values
            X_test = X_test_selected_scaled_df.values
        else:
            X_train =  X_train_selected_df.values
            X_test = X_test_selected_df.values
    else:
        if exp['needs_scaling']:
            X_train = X_train_full_scaled_df.values
            X_test = X_test_full_scaled_df.values
        else:
            X_train = X_train_df.values
            X_test = X_test_df.values
            
    grid_cv.fit(X_train, y_train)
    best_model = grid_cv.best_estimator_
    test_accuracy = best_model.score(X_test, y_test)
    
    results_list.append({
        'Experiment': exp['name'],
        'Best Parameters': grid_cv.best_params_,
        'Best CV Score': grid_cv.best_score_,
        'Test Accuracy': test_accuracy
    })

# Convert summary into DataFrame for notebook presentation
results_df = pd.DataFrame(results_list)
results_df
Running Experiment: KNN_Full_Features
Running Experiment: KNN_Feature_Selection
Running Experiment: DecisionTree_Full_Features
Running Experiment: DecisionTree_Feature_Selection
Running Experiment: RandomForest_Full_Features
Running Experiment: RandomForest_Feature_Selection
Running Experiment: XGBoost_Full_Features
Running Experiment: XGBoost_Feature_Selection
Experiment Best Parameters Best CV Score Test Accuracy
0 KNN_Full_Features {'n_neighbors': 21} 0.646875 0.6850
1 KNN_Feature_Selection {'n_neighbors': 19} 0.803750 0.8175
2 DecisionTree_Full_Features {'criterion': 'entropy', 'max_depth': None} 0.855625 0.8500
3 DecisionTree_Feature_Selection {'criterion': 'entropy', 'max_depth': None} 0.868750 0.8475
4 RandomForest_Full_Features {'criterion': 'entropy', 'max_depth': None, 'n... 0.889375 0.9000
5 RandomForest_Feature_Selection {'criterion': 'gini', 'max_depth': None, 'n_es... 0.913125 0.9275
6 XGBoost_Full_Features {'learning_rate': 0.2, 'max_depth': 3, 'n_esti... 0.915000 0.9175
7 XGBoost_Feature_Selection {'learning_rate': 0.2, 'max_depth': 3, 'n_esti... 0.921875 0.9125

The empirical results from the 8 experimental configurations provide critical insights into algorithmic behavior and the impact of statistical feature selection on high-dimensional tabular data.

Key Findings & Insights:

  1. The Dramatic Impact of Feature Selection on KNN:
    • KNN_Full_Features: Achieved the lowest performance (\(\text{CV: } 64.69\%, \text{ Test: } 68.50\%\)). The presence of 20 baseline features severely degraded distance-based metrics due to the curse of dimensionality and noisy categorical attributes.
    • KNN_Feature_Selection: Performance jumped significantly (\(\text{CV: } 80.38\%, \text{ Test: } 81.75\%\)), yielding a +13.25% improvement in Test Accuracy. Pruning non-informative variables directly refined the Euclidean distance space.
  2. Resilience of Tree-Based Models:
    • Single Decision Trees (DecisionTree) exhibited stable performance (\(\sim 84.75\% - 85.00\%\) Test Accuracy) across both feature sets, proving their native ability to prioritize splits based on Information Gain/Entropy (criterion='entropy').
  3. Dominance of Ensemble Methods (Random Forest & XGBoost):
    • Ensemble frameworks consistently outperformed single estimators, crossing the \(90\%\) accuracy threshold.
    • Random Forest with Feature Selection achieved the highest overall test accuracy of \(92.75\%\) (\(\text{CV: } 91.31\%\)), demonstrating that eliminating low-variance noise features allows bagging ensembles to build higher-quality decision boundaries.
    • XGBoost maintained exceptionally tight convergence between CV score and Test Accuracy (\(\sim 91.25\% - 92.18\%\)), highlighting the robustness of gradient boosting against variance.

When evaluating the top-performing models, a classic trade-off between Peak Test Accuracy and Generalization Stability emerges:

  • XGBoost_Full_Features demonstrates superior variance control, achieving a remarkably narrow generalization gap of \(0.25\%\) (\(\text{CV: } 91.50\% \text{ vs Test: } 91.75\%\)). This confirms that gradient boosting seamlessly manages baseline noise across data splits.
  • RandomForest_Feature_Selection, however, delivers the highest absolute classification accuracy on unseen data (\(92.75\%\)) while maintaining a healthy, non-overfitting gap of \(1.44\%\).

Conclusion: RandomForest_Feature_Selection is recommended as the primary candidate due to its superior predictive capability (\(+1.00\%\) accuracy over XGBoost) and lower feature complexity (adhering to Occam’s Razor by discarding non-informative features). Nonetheless, XGBoost_Full_Features stands as a highly robust alternative for environments prioritizing strict cross-split stability.

best_model_id = 5
best_model_name = results_df['Experiment'].iloc[best_model_id]
best_params = results_df['Best Parameters'].iloc[best_model_id]

print(f'Best Model: {best_model_name}')
print(f'Best Parameters: {best_params}')

model = experiments[best_model_id]['model'].set_params(**best_params)

exp = experiments[best_model_id]
if exp['use_selected_features']:
    if exp['needs_scaling']:
        X_train =  X_train_selected_scaled_df.values
        X_test = X_test_selected_scaled_df.values
    else:
        X_train =  X_train_selected_df.values
        X_test = X_test_selected_df.values
else:
    if exp['needs_scaling']:
        X_train = X_train_full_scaled_df.values
        X_test = X_test_full_scaled_df.values
    else:
        X_train = X_train_df.values
        X_test = X_test_df.values

model.fit(X_train, y_train)
Best Model: RandomForest_Feature_Selection
Best Parameters: {'criterion': 'gini', 'max_depth': None, 'n_estimators': 200}
RandomForestClassifier(n_estimators=200, random_state=42)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
y_pred = model.predict(X_test)
y_pred
array([0, 2, 1, 3, 1, 1, 2, 0, 3, 1, 0, 1, 2, 3, 2, 2, 3, 3, 1, 0, 0, 1,
       1, 2, 0, 1, 3, 2, 2, 0, 0, 0, 3, 0, 1, 1, 2, 0, 3, 0, 2, 3, 2, 0,
       3, 2, 1, 1, 3, 1, 3, 1, 0, 0, 1, 1, 1, 3, 0, 0, 1, 3, 3, 1, 0, 0,
       3, 3, 1, 2, 2, 2, 0, 1, 2, 0, 0, 3, 2, 2, 3, 2, 1, 0, 1, 3, 1, 3,
       3, 0, 3, 3, 2, 1, 3, 2, 2, 3, 1, 1, 0, 0, 1, 0, 0, 3, 2, 0, 1, 1,
       0, 0, 3, 1, 2, 2, 3, 3, 0, 2, 1, 3, 2, 1, 3, 3, 0, 3, 0, 2, 3, 0,
       2, 2, 0, 3, 1, 0, 0, 2, 3, 1, 2, 2, 0, 0, 0, 1, 1, 2, 3, 1, 1, 0,
       2, 2, 0, 1, 0, 1, 2, 3, 3, 2, 1, 0, 1, 2, 2, 3, 3, 1, 1, 0, 3, 1,
       1, 2, 1, 0, 0, 0, 0, 0, 3, 2, 0, 3, 0, 0, 0, 0, 1, 3, 3, 1, 0, 1,
       1, 1, 1, 2, 2, 2, 3, 3, 1, 2, 0, 0, 0, 2, 1, 1, 3, 1, 0, 2, 1, 1,
       3, 2, 3, 0, 0, 2, 1, 2, 0, 1, 2, 0, 2, 3, 2, 1, 1, 3, 3, 0, 1, 3,
       3, 3, 0, 3, 1, 2, 3, 3, 2, 1, 1, 3, 3, 1, 3, 3, 3, 3, 3, 0, 1, 2,
       2, 1, 2, 0, 2, 3, 2, 2, 2, 1, 0, 2, 0, 3, 3, 1, 3, 1, 0, 3, 1, 2,
       0, 0, 3, 0, 1, 2, 3, 3, 3, 1, 1, 0, 1, 3, 3, 0, 1, 2, 2, 0, 3, 3,
       2, 3, 2, 3, 2, 0, 2, 1, 1, 1, 0, 0, 0, 3, 2, 3, 1, 0, 1, 0, 1, 3,
       3, 0, 3, 3, 2, 1, 3, 0, 0, 3, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 1, 0,
       0, 3, 3, 0, 3, 0, 0, 2, 0, 1, 2, 2, 2, 3, 0, 3, 2, 2, 3, 3, 3, 2,
       1, 2, 0, 3, 1, 3, 3, 0, 2, 3, 2, 3, 3, 3, 0, 0, 2, 3, 0, 0, 2, 3,
       2, 1, 1, 2])
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay

print("--- Classification Report ---")
report = classification_report(y_test, y_pred)
print('Classification Report:\n')
print(report)

print("--- Confusion Matrix ---")
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
--- Classification Report ---
Classification Report:

              precision    recall  f1-score   support

           0       0.99      0.95      0.97       105
           1       0.89      0.98      0.93        91
           2       0.89      0.86      0.87        92
           3       0.94      0.92      0.93       112

    accuracy                           0.93       400
   macro avg       0.93      0.93      0.93       400
weighted avg       0.93      0.93      0.93       400

--- Confusion Matrix ---

Save Best Model

import joblib

best_model_name = best_model_name.replace(' ', '_').lower()
joblib.dump(model, f'model/mobiles_{best_model_name}.pkl')
['model/mobiles_randomforest_feature_selection.pkl']

Also Save The Selected Features

with open('model/mobiles_selected_features.txt', mode='w', encoding='utf-8') as f:
    for feature in selected_columns:
        f.write(f'{feature}\n')

Use The Model to Predict Mobiles Price Range for The New Data

df_new = pd.read_csv('data/mobiles_new.csv')
df_new
id battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt ... pc px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi
0 1 1043 1 1.8 1 14 0 5 0.1 193 ... 16 226 1412 3476 12 7 2 0 1 0
1 2 841 1 0.5 1 4 1 61 0.8 191 ... 12 746 857 3895 6 0 7 1 0 0
2 3 1807 1 2.8 0 1 0 27 0.9 186 ... 4 1270 1366 2396 17 10 10 0 1 1
3 4 1546 0 0.5 1 18 1 25 0.5 96 ... 20 295 1752 3893 10 0 7 1 1 0
4 5 1434 0 1.4 0 11 1 49 0.5 108 ... 18 749 810 1773 15 8 7 1 0 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
995 996 1700 1 1.9 0 0 1 54 0.5 170 ... 17 644 913 2121 14 8 15 1 1 0
996 997 609 0 1.8 1 0 0 13 0.9 186 ... 2 1152 1632 1933 8 1 19 0 1 1
997 998 1185 0 1.4 0 1 1 8 0.5 80 ... 12 477 825 1223 5 0 14 1 0 0
998 999 1533 1 0.5 1 0 0 50 0.4 171 ... 12 38 832 2509 15 11 6 0 1 0
999 1000 1270 1 0.5 0 4 1 35 0.1 140 ... 19 457 608 2828 9 2 3 1 0 1

1000 rows × 21 columns

df_new.info()
<class 'pandas.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 21 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   id             1000 non-null   int64  
 1   battery_power  1000 non-null   int64  
 2   blue           1000 non-null   int64  
 3   clock_speed    1000 non-null   float64
 4   dual_sim       1000 non-null   int64  
 5   fc             1000 non-null   int64  
 6   four_g         1000 non-null   int64  
 7   int_memory     1000 non-null   int64  
 8   m_dep          1000 non-null   float64
 9   mobile_wt      1000 non-null   int64  
 10  n_cores        1000 non-null   int64  
 11  pc             1000 non-null   int64  
 12  px_height      1000 non-null   int64  
 13  px_width       1000 non-null   int64  
 14  ram            1000 non-null   int64  
 15  sc_h           1000 non-null   int64  
 16  sc_w           1000 non-null   int64  
 17  talk_time      1000 non-null   int64  
 18  three_g        1000 non-null   int64  
 19  touch_screen   1000 non-null   int64  
 20  wifi           1000 non-null   int64  
dtypes: float64(2), int64(19)
memory usage: 164.2 KB
# drop column id

df_new = df_new.drop(columns=['id'])
# load selected features from the saved txt file
selected_features = []
with open('model/mobiles_selected_features.txt', mode='r', encoding='utf-8') as f:
    for line in f:
        selected_features.append(line.strip())

# filter only selected features and sort the columns in the same order as the training data
df_new = df_new[selected_features].sort_index(axis=1)
df_new
battery_power int_memory mobile_wt px_height px_width ram
0 1043 5 193 226 1412 3476
1 841 61 191 746 857 3895
2 1807 27 186 1270 1366 2396
3 1546 25 96 295 1752 3893
4 1434 49 108 749 810 1773
... ... ... ... ... ... ...
995 1700 54 170 644 913 2121
996 609 13 186 1152 1632 1933
997 1185 8 80 477 825 1223
998 1533 50 171 38 832 2509
999 1270 35 140 457 608 2828

1000 rows × 6 columns

X_new = df_new.values

# load saved model
model = joblib.load('model/mobiles_randomforest_feature_selection.pkl')

# predict the price range for new data
y_new_predict = model.predict(X_new)
y_new_predict
array([3, 3, 2, 3, 1, 3, 3, 1, 3, 0, 3, 3, 0, 0, 2, 0, 2, 1, 3, 2, 1, 3,
       1, 1, 3, 0, 2, 0, 2, 0, 2, 0, 3, 0, 0, 1, 3, 1, 2, 1, 1, 2, 0, 0,
       0, 1, 0, 3, 1, 2, 1, 0, 3, 0, 3, 1, 3, 1, 1, 3, 3, 2, 0, 2, 1, 1,
       1, 3, 1, 2, 1, 2, 2, 3, 3, 0, 2, 0, 2, 3, 0, 3, 3, 0, 3, 0, 3, 1,
       3, 0, 1, 2, 2, 0, 2, 1, 0, 2, 1, 2, 1, 0, 0, 3, 1, 2, 0, 1, 2, 3,
       3, 2, 1, 3, 3, 3, 3, 2, 3, 0, 0, 3, 2, 1, 2, 0, 3, 2, 3, 1, 0, 2,
       1, 1, 3, 1, 1, 0, 3, 2, 1, 3, 1, 2, 2, 3, 3, 2, 2, 3, 2, 3, 0, 0,
       3, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 1, 0, 3, 0, 0, 0, 1, 1, 0, 1,
       0, 0, 1, 2, 1, 0, 0, 1, 1, 2, 2, 1, 0, 0, 0, 1, 0, 3, 2, 0, 2, 2,
       2, 3, 1, 2, 3, 2, 3, 2, 2, 1, 0, 0, 1, 2, 0, 2, 3, 3, 0, 2, 0, 3,
       2, 3, 3, 1, 0, 1, 0, 3, 0, 1, 0, 2, 2, 1, 2, 0, 3, 0, 3, 1, 2, 0,
       0, 2, 1, 3, 3, 3, 1, 1, 3, 0, 0, 2, 3, 3, 1, 3, 1, 1, 3, 2, 1, 2,
       3, 3, 3, 1, 0, 1, 2, 3, 2, 1, 3, 2, 0, 3, 0, 1, 2, 0, 0, 3, 2, 3,
       3, 2, 1, 3, 3, 2, 3, 2, 2, 1, 2, 0, 2, 3, 1, 0, 0, 3, 0, 3, 0, 1,
       2, 0, 2, 3, 1, 3, 2, 2, 1, 2, 0, 0, 0, 1, 3, 2, 0, 0, 0, 3, 2, 0,
       3, 3, 1, 2, 3, 2, 3, 1, 3, 3, 2, 2, 2, 3, 3, 0, 3, 0, 3, 1, 3, 1,
       3, 3, 0, 1, 1, 3, 1, 3, 2, 3, 0, 0, 0, 0, 2, 0, 0, 2, 2, 1, 2, 3,
       2, 0, 1, 0, 0, 3, 2, 0, 3, 1, 2, 2, 1, 2, 3, 1, 1, 2, 2, 1, 2, 0,
       1, 1, 0, 3, 2, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 2, 2, 3, 2, 3, 0, 3,
       0, 3, 0, 1, 1, 1, 1, 0, 3, 2, 3, 3, 1, 3, 1, 3, 1, 3, 2, 0, 2, 2,
       1, 1, 0, 0, 0, 1, 2, 1, 0, 3, 2, 0, 2, 3, 0, 0, 3, 1, 1, 0, 3, 3,
       3, 0, 3, 0, 2, 3, 3, 3, 0, 2, 0, 2, 3, 0, 1, 1, 0, 0, 1, 1, 1, 3,
       3, 3, 2, 3, 1, 1, 2, 2, 3, 3, 2, 0, 2, 2, 2, 2, 1, 0, 2, 2, 0, 0,
       0, 3, 1, 1, 2, 2, 2, 0, 3, 0, 2, 2, 0, 3, 0, 2, 3, 0, 1, 1, 3, 3,
       1, 1, 2, 3, 2, 0, 2, 1, 2, 0, 3, 3, 1, 2, 2, 2, 3, 0, 1, 2, 3, 1,
       3, 2, 3, 1, 1, 1, 0, 3, 1, 0, 3, 2, 3, 2, 0, 3, 3, 3, 2, 3, 3, 1,
       2, 1, 2, 3, 3, 1, 0, 1, 1, 2, 2, 2, 0, 0, 2, 2, 3, 2, 0, 2, 1, 3,
       3, 0, 1, 3, 0, 2, 1, 1, 0, 0, 2, 1, 0, 1, 1, 2, 2, 0, 2, 2, 1, 0,
       3, 0, 0, 3, 2, 0, 0, 0, 0, 0, 3, 0, 3, 1, 3, 2, 1, 3, 2, 0, 1, 1,
       3, 2, 2, 2, 0, 3, 0, 2, 0, 2, 0, 1, 1, 1, 1, 2, 1, 3, 1, 3, 2, 2,
       1, 3, 2, 0, 1, 2, 0, 3, 3, 0, 2, 1, 1, 2, 0, 3, 2, 0, 3, 2, 3, 0,
       0, 3, 0, 2, 2, 3, 2, 2, 2, 2, 1, 2, 3, 0, 1, 1, 1, 2, 1, 0, 0, 1,
       0, 0, 3, 0, 1, 1, 0, 1, 1, 1, 3, 0, 3, 2, 3, 0, 0, 1, 2, 2, 1, 0,
       1, 2, 0, 1, 1, 0, 0, 3, 3, 0, 3, 1, 2, 3, 0, 1, 0, 2, 2, 0, 3, 1,
       0, 3, 0, 1, 0, 3, 3, 3, 2, 3, 0, 3, 2, 0, 1, 0, 3, 3, 2, 0, 2, 1,
       3, 1, 0, 3, 2, 0, 3, 1, 2, 1, 1, 1, 3, 1, 1, 1, 2, 0, 0, 2, 2, 0,
       2, 0, 0, 0, 0, 3, 3, 3, 3, 0, 1, 2, 2, 1, 0, 0, 2, 1, 0, 2, 0, 2,
       2, 2, 1, 2, 0, 2, 1, 3, 0, 0, 3, 1, 3, 0, 0, 2, 3, 3, 1, 2, 2, 1,
       0, 0, 2, 3, 0, 3, 0, 0, 0, 2, 2, 1, 3, 0, 3, 2, 1, 2, 3, 3, 0, 1,
       1, 2, 1, 2, 2, 0, 1, 3, 1, 1, 3, 1, 2, 3, 1, 1, 1, 1, 3, 3, 0, 2,
       3, 0, 2, 3, 2, 2, 2, 3, 2, 0, 1, 2, 0, 2, 1, 1, 2, 2, 2, 1, 2, 1,
       1, 1, 3, 1, 0, 1, 2, 3, 1, 0, 0, 3, 2, 2, 3, 0, 3, 3, 2, 1, 3, 0,
       1, 3, 1, 2, 1, 2, 3, 2, 0, 3, 0, 2, 3, 0, 3, 2, 2, 3, 1, 0, 2, 3,
       1, 0, 2, 1, 2, 1, 3, 0, 2, 2, 0, 2, 3, 2, 3, 0, 2, 1, 1, 2, 2, 3,
       3, 0, 2, 1, 2, 1, 3, 0, 1, 3, 0, 1, 0, 0, 3, 2, 2, 0, 0, 0, 0, 3,
       2, 3, 3, 0, 0, 2, 1, 0, 2, 2])
df_new['predicted_price_range'] = y_new_predict
df_new
battery_power int_memory mobile_wt px_height px_width ram predicted_price_range
0 1043 5 193 226 1412 3476 3
1 841 61 191 746 857 3895 3
2 1807 27 186 1270 1366 2396 2
3 1546 25 96 295 1752 3893 3
4 1434 49 108 749 810 1773 1
... ... ... ... ... ... ... ...
995 1700 54 170 644 913 2121 2
996 609 13 186 1152 1632 1933 1
997 1185 8 80 477 825 1223 0
998 1533 50 171 38 832 2509 2
999 1270 35 140 457 608 2828 2

1000 rows × 7 columns

# save results to a new csv file
df_new.to_csv('data/mobiles_new_predicted.csv', index=False)