캐글의 [Laptop Specifications and Price Prediction Dataset] 을 이용하여 랩탑 가격을 예측하는 선형회귀 모델을 만들었다.
Laptop Specifications and Price Prediction Dataset
Dataset About Comprehensive Laptop Specifications and Price Prediction Dataset
www.kaggle.com
데이터 로드 및 확인
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore', category=UserWarning)
df = pd.read_csv("./data/laptop_data.csv")
df

df.info()

df.describe()

df.isnull().sum()

데이터 시각화
numerical_cols = ["Unnamed: 0", "Inches", "Price"]
categorical_cols = [
"Company",
"TypeName",
"ScreenResolution",
"Cpu",
"Ram",
"Memory",
"Gpu",
"OpSys",
"Weight"
]
# 수치형 데이터 시각화
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for i, col_name in enumerate(numerical_cols):
ax = axes[i]
# 히스토그램 그리기
ax.hist(df[col_name], bins=10, color="C"+str(i), alpha=0.7, edgecolor="black")
# 개별 차트 제목 및 라벨 설정
ax.set_title(f"Histogram: {col_name}")
ax.set_xlabel("Values")
ax.set_ylabel("Frequency")
# 격자선 추가
ax.grid(axis="y", alpha=0.5)
plt.tight_layout()
plt.show()

# 범주형 데이터 시각화
fig, axes = plt.subplots(3, 3, figsize=(20, 20))
axes_flat = axes.flatten()
for i, col_name in enumerate(categorical_cols):
ax = axes_flat[i]
# 카테고리 수가 많아서 상위 15개만 뽑고 차트를 그림
data_counts = df[col_name].value_counts().head(15)
ax.bar(data_counts.index.astype(str), data_counts.values, color="C"+str(i), alpha=0.7, edgecolor="black")
ax.set_title(f"Bar Chart: {col_name}")
ax.set_ylabel("Count")
ax.tick_params(axis="x", rotation=90) # x축 글자가 겹치므로 90도 회전
plt.tight_layout()
plt.show()

for col in categorical_cols:
print(f"===== {col} : {df[col].nunique()} =====")
catetories = df[col].unique().tolist()
print(catetories)

데이터값의 종류가 상당히 많은 컬럼들이 존재한다. Cpu, Gpu 등은 브랜드, 속도, 모델명등이 다양하기 때문에 100종류가 넘는 값을 가지고 있는데, 이는 추후 특징을 추출하여 단순화해야 할 필요가 있다.
plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='Company', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='TypeName', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='Ram', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='Memory', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='OpSys', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(15,10))
sns.boxplot(data=df, x='ScreenResolution', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(15,10))
sns.boxplot(data=df, x='Cpu', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

plt.figure(figsize=(15,10))
sns.boxplot(data=df, x='Gpu', y='Price')
plt.tick_params(axis="x", rotation=90)
plt.tight_layout()
plt.show()

# Weight 값이 모두 숫자로 시작해서 kg 문자열로 끝나는지 확인
# ^\d+ : 시작이 숫자로 이루어짐
# (\.\d+)? : 선택적으로 소수점이 올 수 있음
# Kg$ : 끝이 'Kg'로 끝남
pattern = r"^\d+(\.\d+)?kg$"
df[~df["Weight"].str.contains(pattern, na=False)]
invalid_data = df[~df['Weight'].str.contains(pattern, na=False)]
print(invalid_data)

# Ram 값이 모두 숫자로 시작해서 GB 문자열로 끝나는지 확인
# ^\d+ : 시작이 숫자로 이루어짐
# GB$ : 끝이 'GB'로 끝남
pattern = r"^\d+GB$"
df[~df["Ram"].str.contains(pattern, na=False)]
invalid_data = df[~df['Ram'].str.contains(pattern, na=False)]
print(invalid_data)

데이터 특징 정리 및 전처리
1. 가격대는 왼쪽(저렴한 가격)으로 쏠려 있다.
2. 게이밍, 워크스테이션 노트북이 주로 높은 가격대를 형성하고 있다.
3. 램 용량은 가격와 양의 상관관계를 보인다.
4. 4K 해상도 노트북이 주로 높은 가격대를 형성하고 있다.
5. CPU, GPU는 종류가 매우 다양하기 때문에 데이터 정제 및 압축이 필요하다.
6. [Unnamed: 0] 컬럼은 삭제
7. [Weight] 컬럼은 숫자로 시작하고 "Kg"으로 끝나므로, "Kg" 문자열은 삭제
8. [Ram] 컬럼은 숫자로 시작하고 "GB"로 끝나므로 "GB" 문자열은 삭제
df_drop = df.drop(labels=["Unnamed: 0"], axis=1)
df_drop['Weight'] = df_drop['Weight'].str.replace("kg", "").astype(float)
df_drop['Ram'] = df_drop['Ram'].str.replace("GB", "").astype(int)
df_drop.head()

from sklearn.preprocessing import OneHotEncoder
# Company, TypeName, OpSys 는 원핫인코딩
encoder = OneHotEncoder(sparse_output=False)
encoded_data = encoder.fit_transform(df_drop[["Company", "TypeName", "OpSys"]])
# 인코딩 된 데이터를 데이터프레임으로 변환
encoded_df = pd.DataFrame(
encoded_data, columns=encoder.get_feature_names_out(["Company", "TypeName", "OpSys"])
)
# 기존 데이터셋에서 인코딩한 컬럼을 제거하고 인코딩된 데이터를 붙이기
df_encoded = df_drop.drop(columns=["Company", "TypeName", "OpSys"])
df_encoded = pd.concat([df_encoded, encoded_df], axis=1)
# ScreenResolution은 IPS패널, 터치스크린, 레티나, [X해상도xY해상도] 세 가지 대표값으로 분리 (추후 삭제))
# 1. 해상도 분리
df_encoded["X_res"] = df_encoded["ScreenResolution"].str.extract(r"(\d+)x\d+").astype(int)
df_encoded["Y_res"] = df_encoded["ScreenResolution"].str.extract(r"\d+x(\d+)").astype(int)
# 2. IPS 패널 여부
df_encoded["IPS"] = df_encoded["ScreenResolution"].str.contains("IPS").astype(int)
# 3. 터치스크린 여부
df_encoded["TouchScreen"] = df_encoded["ScreenResolution"].str.contains("TouchScreen").astype(int)
# 4. 레티나 디스플레이 여부
df_encoded["Retina"] = df_encoded["ScreenResolution"].str.contains("Retina").astype(int)
import re
# 메모리 용량을 처리하기 위한 로직: 모든 값을 GB 단위로 변환
def convert_to_gb(value):
value = value.upper()
if "TB" in value:
num = float(re.findall(r"(\d+\.?\d*)", value)[0])
return num * 1000
elif "GB" in value:
num = float(re.findall(r"(\d+\.?\d*)", value)[0])
return num
return 0
# 타입별로 용량 합산 (문자열을 + 기준으로 나눠서 각 저장장치의 용량과 타입을 파악)
def parse_storage(row):
ssd, hdd, flash, hybrid = 0, 0, 0, 0
parts = row.split("+")
for part in parts:
part = part.strip()
gb = convert_to_gb(part)
if "SSD" in part:
ssd += gb
elif "HDD" in part:
hdd += gb
elif "Flash" in part:
flash += gb
elif "Hybrid" in part:
hybrid += gb
return pd.Series([ssd, hdd, flash, hybrid, ssd + hdd + flash + hybrid])
# 데이터 적용
df_encoded[["SSD_GB", "HDD_GB", "Flash_GB", "Hybrid_GB", "Total_GB"]] = df_encoded["Memory"].apply(parse_storage)
# CPU 데이터 압축 전략
# 브랜드, 라인업, 클럭 속도 추출하여 분리 저장
def parse_cpu(cpu_string):
# 1. 클럭 속도 추출 (마지막에 오는 숫자 + GHz)
speed = float(re.findall(r"(\.?\d*)GHz", cpu_string)[0])
# 2. 브랜드 추철 (문자열 첫 단어)
brand = cpu_string.split()[0]
# 3. 모델 라인업 분류
if "Core i7" in cpu_string:
lineup = "Core i7"
elif "Core i5" in cpu_string:
lineup = "Core i5"
elif "Core i3" in cpu_string:
lineup = "Core i3"
elif "Celeron" in cpu_string:
lineup = "Celeron"
elif "Pentium" in cpu_string:
lineup = "Pentium"
elif "Atom" in cpu_string:
lineup = "Atom"
elif "Core M" in cpu_string:
lineup = "Core M"
elif "Xeon" in cpu_string:
lineup = "Xeon"
elif "A9-Series" in cpu_string:
lineup = "A9-Series"
elif "A6-Series" in cpu_string:
lineup = "A6-Series"
elif "A8-Series" in cpu_string:
lineup = "A8-Series"
elif "A12-Series" in cpu_string:
lineup = "A12-Series"
elif "A10-Series" in cpu_string:
lineup = "A10-Series"
elif "FX" in cpu_string:
lineup = "FX"
elif "E-Series" in cpu_string:
lineup = "E2-Series"
elif "Ryzen" in cpu_string:
lineup = "Ryzen"
else:
lineup = "Other"
return pd.Series([brand, lineup, speed])
# 데이터 적용
df_encoded[["CPU_Brand", "CPU_Lineup", "CPU_Speed"]] = df_encoded["Cpu"].apply(parse_cpu)
# Gpu 데이터 압축 전략
# 브랜드, 성능 등급화한 값을 추출하여 분리 저장
# 브랜드: Intel, Nvidia, AMD, ARM
def categorize_gpu(gpu_string):
gpu_string = gpu_string.upper()
# 1. 제조사 추출
if "NVIDIA" in gpu_string:
brand = "NVIDIA"
elif "AMD" in gpu_string:
brand = "AMD"
elif "INTEL" in gpu_string:
brand = "INTEL"
else:
brand = "Other"
# 성능 등급 추출 및 단순화
if "GTX" in gpu_string:
model = "GTX"
elif "UHD" in gpu_string:
model = "UHD"
elif "HD" in gpu_string:
model = "HD"
elif "MX" in gpu_string:
model = "MX"
elif "QUADRO" in gpu_string:
model = "QUADRO"
elif "RADEON" in gpu_string:
model = "RADEON"
elif "IRIS" in gpu_string:
model = "IRIS"
else:
model = "Other"
return pd.Series([brand, model])
# 데이터 적용
df_encoded[["GPU_Brand", "GPU_ModelType"]] = df_encoded["Gpu"].apply(categorize_gpu)
# 전처리를 끝낸 컬럼 삭제
df_encoded = df_encoded.drop(labels=["ScreenResolution", "Cpu", "Memory", "Gpu", "Weight"], axis=1)
# 전처리 후 문자열 형태로 남아 있는 컬럼은 모두 원핫 인코딩
encoder2 = OneHotEncoder(sparse_output=False)
encoded_data2 = encoder2.fit_transform(df_encoded[["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"]])
# 인코딩 된 데이터를 데이터프레임으로 변환
encoded_df2 = pd.DataFrame(
encoded_data2, columns=encoder2.get_feature_names_out(["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"])
)
# 기존 데이터셋에서 인코딩한 컬럼을 제거하고 인코딩된 데이터를 붙이기
df_encoded = df_encoded.drop(columns=["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"])
df_encoded = pd.concat([df_encoded, encoded_df2], axis=1)
df_encoded

학습데이터, 테스트데이터 분리 및 모델 학습
from sklearn.model_selection import train_test_split
X = df_encoded.drop(labels=["Price"], axis=1)
y = df_encoded["Price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
from sklearn.tree import DecisionTreeRegressor
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
lr = LinearRegression()
dtr = DecisionTreeRegressor()
rfr = RandomForestRegressor()
xgbr = XGBRegressor()
from sklearn.metrics import mean_squared_error, r2_score
def model_eval(model):
print(f"===== Model {model} Evaluation =====")
print(f"Train: {model.score(X_train, y_train)}")
print(f"Test: {model.score(X_test, y_test)}")
predictions = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, predictions))
r2 = r2_score(y_test, predictions)
print(f"RMSE: {rmse}")
print(f"R2 Score: {r2}")
print("=" * 50)
lr.fit(X_train, y_train)
dtr.fit(X_train, y_train)
rfr.fit(X_train, y_train)
xgbr.fit(X_train, y_train)
모델 평가
models = [lr, dtr, rfr, xgbr]
for model in models:
model_eval(model)

모델 저장
import joblib
joblib.dump(rfr, "./my_rfr_model.pkl")
가상으로 만든 데이터를 이용하여 예측하기
# price 값이 없는 가상의 데이터를 만들어서 예측
df = pd.read_csv("./data/real_data.csv")
df.head()

# 기존과 동일한 전처리 과정을 진행
df_drop = df.drop(labels=["Unnamed: 0"], axis=1)
df_drop['Weight'] = df_drop['Weight'].str.replace("kg", "").astype(float)
df_drop['Ram'] = df_drop['Ram'].str.replace("GB", "").astype(int)
df_drop.head()
encoded_data = encoder.transform(df_drop[["Company", "TypeName", "OpSys"]])
encoded_df = pd.DataFrame(
encoded_data, columns=encoder.get_feature_names_out(["Company", "TypeName", "OpSys"])
)
df_encoded = df_drop.drop(columns=["Company", "TypeName", "OpSys"])
df_encoded = pd.concat([df_encoded, encoded_df], axis=1)
df_encoded
df_encoded["X_res"] = df_encoded["ScreenResolution"].str.extract(r"(\d+)x\d+").astype(int)
df_encoded["Y_res"] = df_encoded["ScreenResolution"].str.extract(r"\d+x(\d+)").astype(int)
df_encoded["IPS"] = df_encoded["ScreenResolution"].str.contains("IPS").astype(int)
df_encoded["TouchScreen"] = df_encoded["ScreenResolution"].str.contains("TouchScreen").astype(int)
df_encoded["Retina"] = df_encoded["ScreenResolution"].str.contains("Retina").astype(int)
df_encoded[["SSD_GB", "HDD_GB", "Flash_GB", "Hybrid_GB", "Total_GB"]] = df_encoded["Memory"].apply(parse_storage)
df_encoded[["CPU_Brand", "CPU_Lineup", "CPU_Speed"]] = df_encoded["Cpu"].apply(parse_cpu)
df_encoded[["GPU_Brand", "GPU_ModelType"]] = df_encoded["Gpu"].apply(categorize_gpu)
df_encoded = df_encoded.drop(labels=["ScreenResolution", "Cpu", "Memory", "Gpu", "Weight"], axis=1)
encoded_data2 = encoder2.transform(df_encoded[["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"]])
encoded_df2 = pd.DataFrame(
encoded_data2, columns=encoder2.get_feature_names_out(["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"])
)
df_encoded = df_encoded.drop(columns=["CPU_Brand", "CPU_Lineup", "GPU_Brand", "GPU_ModelType"])
df_encoded = pd.concat([df_encoded, encoded_df2], axis=1)
df_encoded

predictions = rfr.predict(df_encoded)
predictions

df["Price"] = predictions
df.to_csv("./result.csv")
print("파일이 저장되었습니다.")'AICE' 카테고리의 다른 글
| [AICE PROFESSIONAL 준비] 아보카도 가격 예측 (0) | 2026.06.18 |
|---|---|
| [AICE PROFESSIONAL 준비] 타이타닉 생존자 예측 (0) | 2026.06.18 |
| [AICE PROFESSIONAL 준비] 토마토 질병 분류 (0) | 2026.06.16 |
| [AICE PROFESSIONAL 준비] 한국어 혐오 댓글 (0) | 2026.06.16 |
| [AICE PROFESSIONAL 준비] 뇌졸증 예측 (0) | 2026.06.11 |