데이터 분석가:Applied Data Analytics/판다스 데이터분석

3. Data transformation

데이터분석 2025. 2. 12. 18:05
320x100
728x90

해당 내용은 모두연의 프로덕트데이터분석가1기 수료과정 중 내용과 개인적인 내용을 정리한 것입니다.

 

 

 

데이터 병합, 개요 작성, 결측치 및 이상치 처리, 집계와 피벗 테이블 활용, 로그 변환 및 원-핫 인코딩, 스케일링, 주성분 분석(PCA)을 통해 데이터 변환의 고급 기술.

  1. 데이터를 다양한 방법으로 합치고 변환할 수 있다.
  2. 데이터의 스케일을 변환할 수 있다.
  3. 카테고리형 데이터를 숫자형태로 변환할 수 있다.
  4. 데이터의 차원축소를 할 수 있다.

JOIN 종류: INNER, OUTER, LEFT, RIGHT

INNER JOIN (교집합)

"공통된 데이터만 가져온다"

  • A와 B에 모두 존재하는 승객만 선택
  • A, B 둘 다 포함된 승객만 출력됨.

예제:

승객 ID / (A)승객 이름

1 Alice
2 Bob
3 Charlie

승객 ID / (B)결제 금액

2 $20
3 $35
4 $50

INNER JOIN 결과:

승객 ID/ 승객 이름 / 결제 금액

2 Bob $20
3 Charlie $35

요점: A, B 둘 다 존재하는 데이터만 남김.


LEFT JOIN (A 우선, B가 없어도 포함)

"A(왼쪽)에 있는 데이터는 전부 포함하고, B에 없는 데이터는 NULL로 채운다"

  • A 테이블의 모든 승객을 포함
  • B 테이블에 없는 승객은 결제 금액을 NULL 처리

예제:
LEFT JOIN 결과:

승객 ID / 승객 이름 / 결제 금액

1 Alice NULL
2 Bob $20
3 Charlie $35

요점: A(왼쪽)의 모든 값 유지, B(오른쪽)에 없으면 NULL


RIGHT JOIN (B 우선, A가 없어도 포함)

"B(오른쪽)에 있는 데이터는 전부 포함하고, A에 없는 데이터는 NULL로 채운다"

  • B 테이블의 모든 결제를 포함
  • A 테이블에 없는 승객은 이름이 NULL 처리

예제:
RIGHT JOIN 결과:

승객 ID / 승객 이름 / 결제 금액

2 Bob $20
3 Charlie $35
4 NULL $50

요점: B(오른쪽)의 모든 값 유지, A(왼쪽)에 없으면 NULL


OUTER JOIN (전체 합집합)

"A, B 모든 데이터 포함하고, 없는 부분은 NULL"

  • 모든 승객(A) + 모든 결제(B)
  • A 또는 B 중 하나라도 존재하면 포함

예제:
OUTER JOIN 결과:

승객 ID / 승객 이름 / 결제 금액

1 Alice NULL
2 Bob $20
3 Charlie $35
4 NULL $50

요점: A, B 모든 데이터 유지, 없는 부분은 NULL


JOIN 유형 / 설명 / 포함되는 데이터

INNER 공통 데이터만 포함 A ∩ B (교집합)
LEFT A 우선, B 없으면 NULL A ⭢ (A + B)
RIGHT B 우선, A 없으면 NULL B ⭢ (A + B)
OUTER 전체 포함, 없으면 NULL A ∪ B (합집합)

기억하는 방법:

  • INNER JOIN = 둘 다 있어야 OK (교집합)
  • LEFT JOIN = A는 무조건 포함 (NULL 허용)
  • RIGHT JOIN = B는 무조건 포함 (NULL 허용)
  • OUTER JOIN = A + B 전부 포함

 

개요, 결측치&이상치(Missing Value & Outlier), 집계 및 그룹화(Aggregation and Group by), 피벗테이블(Pivot)

salary_df.head()

salary_df.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 6684 entries, 0 to 6683
Data columns (total 10 columns):
 #   Column               Non-Null Count  Dtype  
---  ------               --------------  -----  
 0   Age                  6680 non-null   float64
 1   Gender               6684 non-null   object 
 2   Education Level      6684 non-null   int64  
 3   Job Title            6684 non-null   object 
 4   Years of Experience  6684 non-null   float64
 5   Salary               6684 non-null   int64  
 6   Country              6684 non-null   object 
 7   Race                 6684 non-null   object 
 8   Senior               6684 non-null   int64  
 9   CPI                  6684 non-null   object 
dtypes: float64(2), int64(3), object(5)
memory usage: 574.4+ KB
salary_df[
<class 'pandas.core.frame.DataFrame'>
Int64Index: 6684 entries, 0 to 6683
Data columns (total 10 columns):
 #   Column               Non-Null Count  Dtype  
---  ------               --------------  -----  
 0   Age                  6680 non-null   float64
 1   Gender               6684 non-null   object 
 2   Education Level      6684 non-null   int64  
 3   Job Title            6684 non-null   object 
 4   Years of Experience  6684 non-null   float64
 5   Salary               6684 non-null   int64  
 6   Country              6684 non-null   object 
 7   Race                 6684 non-null   object 
 8   Senior               6684 non-null   int64  
 9   CPI             'CPI'] = pd.to_numeric(salary_df['CPI'])  #숫자로 바꿔준다

 

 
salary_df.describe()
Out[68]:
AgeEducation LevelYears of ExperienceSalarySeniorCPIcountmeanstdmin25%50%75%max
6680.000000 6684.000000 6684.000000 6684.000000 6684.000000 6684.00000
33.611527 1.622382 8.084007 115307.175194 0.143477 167.12953
7.595506 0.880474 6.097824 52806.810881 0.350585 73.22657
21.000000 0.000000 -1.000000 350.000000 0.000000 100.00000
28.000000 1.000000 3.000000 70000.000000 0.000000 132.00000
32.000000 1.000000 7.000000 115000.000000 0.000000 135.30000
38.000000 2.000000 12.000000 160000.000000 0.000000 158.70000
62.000000 3.000000 82.000000 250000.000000 1.000000 307.48000
 
salary_df.isna().sum()
Age                    4
Gender                 0
Education Level        0
Job Title              0
Years of Experience    0
Salary                 0
Country                0
Race                   0
Senior                 0
CPI                    0
dtype: int64
salary_df.isna().mean()
Age                    0.000598
Gender                 0.000000
Education Level        0.000000
Job Title              0.000000
Years of Experience    0.000000
Salary                 0.000000
Country                0.000000
Race                   0.000000
Senior                 0.000000
CPI                    0.000000
dtype: float64
salary_df[salary_df['Age'].isna()]    #Age에 결측치가 있는지 알아보는
 
AgeGenderEducation LevelJob TitleYears of ExperienceSalaryCountryRaceSeniorCPI548213527493826
NaN Male 1 Data Analyst 3.0 130000 Canada White 0 158.70
NaN Female 3 Project Engineer 16.0 190000 USA African American 1 307.48
NaN Female 1 Software Engineer 1.0 50000 Australia White 0 135.30
NaN Female 2 Marketing Coordinator 8.0 85000 UK Asian 0 132.00
salary_df = salary_df.dropna()
 
salary_df = salary_df[salary_df['Years of Experience'] != -1]
 
salary_df['Years of Experience'].sort_values()
4931     0.0
5104     0.0
5115     0.0
5119     0.0
5143     0.0
        ... 
2396    33.0
2391    33.0
2490    34.0
2415    34.0
564     82.0
Name: Years of Experience, Length: 6677, dtype: float64
salary_df[salary_df['Years of Experience'] == 82]
:
AgeGenderEducation LevelJob TitleYears of ExperienceSalaryCountryRaceSeniorCPI564
25.0 Female 1 Data Analyst 82.0 110000 Australia White 0 135.3
salary_df = salary_df[~(salary_df['Years of Experience'] > salary_df['Age'] - 18)]
 
salary_df.describe()
 
AgeEducation LevelYears of ExperienceSalarySeniorCPIcountmeanstdmin25%50%75%max
6674.000000 6674.000000 6674.000000 6674.000000 6674.000000 6674.000000
33.612826 1.622116 8.076491 115294.402307 0.143093 167.164774
7.594369 0.880460 6.029750 52819.326323 0.350193 73.241730
21.000000 0.000000 0.000000 350.000000 0.000000 100.000000
28.000000 1.000000 3.000000 70000.000000 0.000000 132.000000
32.000000 1.000000 7.000000 115000.000000 0.000000 135.300000
38.000000 2.000000 12.000000 160000.000000 0.000000 158.700000
62.000000 3.000000 34.000000 250000.000000 1.000000 307.480000
salary_df[salary_df['Years of Experience']  == 0]
 
AgeGenderEducation LevelJob TitleYears of ExperienceSalaryCountryRaceSeniorCPI18498224162430...61796193620762216235
25.0 Female 1 Data Entry Clerk 0.0 35000 UK Asian 0 132.00
25.0 Male 1 Help Desk Analyst 0.0 35000 USA Asian 0 307.48
25.0 Male 1 Sales Representative 0.0 30000 Australia Asian 0 135.30
24.0 Male 2 Back end Developer 0.0 55538 USA Asian 0 307.48
22.0 Female 0 Back end Developer 0.0 51832 UK White 0 132.00
... ... ... ... ... ... ... ... ... ...
24.0 Female 0 Receptionist 0.0 25000 China White 0 100.00
24.0 Female 0 Receptionist 0.0 25000 Australia Australian 0 135.30
24.0 Female 0 Receptionist 0.0 25000 Australia White 0 135.30
24.0 Female 0 Receptionist 0.0 25000 UK Welsh 0 132.00
24.0 Female 0 Receptionist 0.0 25000 Australia Asian 0 135.30

120 rows × 10 columns

salary_df.head()
 
AgeGenderEducation LevelJob TitleYears of ExperienceSalaryCountryRaceSeniorCPI01234
32.0 Male 1 Software Engineer 5.0 90000 UK White 0 132.00
28.0 Female 2 Data Analyst 3.0 65000 USA Hispanic 0 307.48
45.0 Male 3 Manager 15.0 150000 Canada White 1 158.70
36.0 Female 1 Sales Associate 7.0 60000 USA Hispanic 0 307.48
52.0 Male 2 Director 20.0 200000 USA Asian 0 307.48
salary_df[salary_df['Gender'] == "Male"]['Salary'].mean()
121383.05728314239
salary_df[salary_df['Gender'] == "Female"]['Salary'].mean()
107873.85405585106
salary_df.groupby('Gender').mean()
 
AgeEducation LevelYears of ExperienceSalarySeniorCPIGenderFemaleMale
32.622008 1.600066 7.417221 107873.854056 0.127992 167.485166
34.425805 1.640207 8.617430 121383.057283 0.155483 166.901888
salary_df.groupby('Gender').max()
 
AgeEducation LevelJob TitleYears of ExperienceSalaryCountryRaceSeniorCPIGenderFemaleMale
60.0 3 Web Developer 34.0 220000 USA White 1 307.48
62.0 3 Web Developer 32.0 250000 USA White 1 307.48
salary_df.groupby('Gender')['Salary'].mean()
Gender
Female    107873.854056
Male      121383.057283
Name: Salary, dtype: float64
salary_df.groupby('Gender')['Salary'].sum()
Gender
Female    324484553
Male      444990288
Name: Salary, dtype: int64
salary_df.groupby('Gender')['Salary'].median()
Gender
Female    105000.0
Male      120000.0
Name: Salary, dtype: float64
salary_df.groupby('Gender')['Salary'].std()
Gender
Female    52728.350439
Male      52117.611899
Name: Salary, dtype: float64
salary_df.groupby(['Gender','Country'])['Salary'].mean()
Gender  Country  
Female  Australia    107936.054010
        Canada       106884.711340
        China        111291.211506
        UK           108495.273026
        USA          104854.691558
Male    Australia    120896.764216
        Canada       123973.921516
        China        120135.522148
        UK           122244.048476
        USA          119683.120433
Name: Salary, dtype: float64
salary_df.groupby('Gender')['Salary'].agg(['sum','mean'])
 
summeanGenderFemaleMale
324484553 107873.854056
444990288 121383.057283
salary_df.groupby(['Gender','Country'])['Salary'].mean().reset_index()
 
GenderCountrySalary0123456789
Female Australia 107936.054010
Female Canada 106884.711340
Female China 111291.211506
Female UK 108495.273026
Female USA 104854.691558
Male Australia 120896.764216
Male Canada 123973.921516
Male China 120135.522148
Male UK 122244.048476
Male USA 119683.120433
pd.pivot_table(salary_df, index = 'Gender', columns = 'Country', values = 'Salary')
 
CountryAustraliaCanadaChinaUKUSAGenderFemaleMale
107936.054010 106884.711340 111291.211506 108495.273026 104854.691558
120896.764216 123973.921516 120135.522148 122244.048476 119683.120433
pd.pivot_table(salary_df, index = 'Gender', columns = 'Country', values = 'Salary', aggfunc = 'mean')
 
CountryAustraliaCanadaChinaUKUSAGenderFemaleMale
107936.054010 106884.711340 111291.211506 108495.273026 104854.691558
120896.764216 123973.921516 120135.522148 122244.048476 119683.120433
pd.pivot_table(salary_df, index = 'Gender', columns = 'Country', values = 'Salary', aggfunc = np.mean)
 
CountryAustraliaCanadaChinaUKUSAGenderFemaleMale
107936.054010 106884.711340 111291.211506 108495.273026 104854.691558
120896.764216 123973.921516 120135.522148 122244.048476 119683.120433
pd.pivot_table(salary_df, index = 'Gender', columns = 'Country', values = 'Salary', aggfunc = 'sum')
 
CountryAustraliaCanadaChinaUKUSAGenderFemaleMale
65948929 62206902 65773106 65965126 64590490
87166567 91616728 89500964 88260203 88445826
pd.pivot_table(salary_df, index = ['Gender','Race'], columns = 'Country', values = 'Salary', aggfunc = 'sum')
 
CountryAustraliaCanadaChinaUKUSAGenderRaceFemaleAfrican AmericanAsianAustralianBlackChineseHispanicKoreanMixedWelshWhiteMaleAfrican AmericanAsianAustralianBlackChineseHispanicKoreanMixedWelshWhite
NaN NaN NaN NaN 15448789.0
22620268.0 20950962.0 NaN 14978857.0 17533654.0
22623208.0 NaN NaN NaN NaN
NaN 20851624.0 NaN NaN NaN
NaN NaN 20858851.0 NaN NaN
NaN NaN NaN NaN 14526773.0
NaN NaN 23853851.0 NaN NaN
NaN NaN NaN 15673240.0 NaN
NaN NaN NaN 17882663.0 NaN
20705453.0 20404316.0 21060404.0 17430366.0 17081274.0
NaN NaN NaN NaN 23910271.0
31837948.0 31446774.0 NaN 24308468.0 20421501.0
29011770.0 NaN NaN NaN NaN
NaN 30659174.0 NaN NaN NaN
NaN NaN 28164994.0 NaN NaN
NaN NaN NaN NaN 21025292.0
NaN NaN 29660738.0 NaN NaN
NaN NaN NaN 23181267.0 NaN
NaN NaN NaN 19600032.0 NaN
26316849.0 29510780.0 31675232.0 21170436.0 23088762.0
sales_df = pd.DataFrame({'company': ['a','a','a','a','b','b','b','b'],
             'quarter': ['q1','q2','q3','q4','q1','q2','q3','q4'],
             'sales': [111,222,333,444,555,666,777,888]})
 
sales_df
 
companyquartersales01234567
a q1 111
a q2 222
a q3 333
a q4 444
b q1 555
b q2 666
b q3 777
b q4 888
sales_temp = pd.pivot(sales_df, index = 'company', columns = 'quarter', values = 'sales')
 
sales_temp.columns = sales_temp.columns.rename('')
 
new_sales_df = sales_temp.reset_index()
 
new_sales_df
 
companyq1q2q3q401
a 111 222 333 444
b 555 666 777 888
pd.melt(new_sales_df, id_vars = 'company', value_vars = ['q1','q2','q3','q4'], var_name ='quarter' ,value_name = 'sales').sort_values('company')
:
companyquartersales02461357
a q1 111
a q2 222
a q3 333
a q4 444
b q1 555
b q2 666
b q3 777
b q4 888

주성분 분석 (PCA) (Principal Component Analysis)

1. PCA란?

PCA(주성분 분석, Principal Component Analysis)는 데이터의 차원을 줄이면서도 중요한 정보(변동성)를 최대한 유지하는 기법이다.
즉, 고차원의 데이터를 저차원으로 압축하지만, 핵심 정보를 최대한 유지하려는 방법이다.

2. 왜 PCA가 필요할까?

  • 데이터의 차원이 너무 높으면(변수가 많으면) 분석이 어렵고 계산량이 많아진다.
  • 차원을 줄이면 데이터 시각화가 쉬워지고, 모델 성능도 개선될 수 있다.
  • 데이터에서 가장 중요한 축(주성분)을 찾아 정보를 최대한 보존하면서 단순화할 수 있다.

3. PCA 작동 원리 (한 줄 정리)

데이터의 분포를 따라 가장 중요한 방향(축)을 찾고, 그 축을 기준으로 데이터를 변환(압축)하는 것이다.

4. PCA가 데이터를 변환하는 과정

  1. 데이터 정규화 (평균 0, 분산 1로 맞춤)
    • 변수들의 단위 차이(예: cm, kg)를 맞추기 위해 데이터를 표준화한다.
  2. 공분산 행렬 계산 (변수 간 관계 분석)
    • 어떤 변수들이 함께 변하는지를 확인하여 서로 상관성이 높은 축을 찾는다.
  3. 고유값 & 고유벡터 계산
    • 데이터를 가장 잘 설명하는 새로운 축(주성분)을 찾기 위해 고유값 분해 또는 SVD(특이값 분해)를 수행한다.
  4. 주성분 선택
    • 고유값이 큰 순서대로 가장 중요한 주성분(Principal Components)을 선택한다.
    • 보통 전체 정보의 90% 이상을 유지하는 개수의 주성분을 선택한다.
  5. 데이터 변환 (기존 축 → 주성분 축)
    • 기존 변수 대신, 선택한 주성분들로 데이터를 다시 표현하여 차원 축소를 수행한다.

5. PCA를 쉽게 비유하면?

PCA는 사진을 압축하는 과정과 비슷하다.

  • 원본 이미지는 고해상도(변수가 많음)
  • 하지만 용량이 크면 부담스러우니 적절히 압축하면서 화질(정보)를 최대한 유지해야 한다.
  • PCA는 데이터에서 가장 중요한 특징(주성분)만 남기고 나머지는 제거하는 역할을 한다.

6. PCA의 한계

  • 주성분이 원래 변수들의 조합이므로, 해석이 어려울 수 있다.
  • 데이터의 선형적인 패턴을 기반으로 하므로, 비선형 데이터에는 잘 맞지 않을 수 있다.

7. PCA를 언제 사용할까?

  • 데이터의 차원이 너무 높아서 시각화가 어렵거나, 계산량이 많을 때
  • 변수 간 상관성이 높아 중복된 정보가 많을 때
  • 머신러닝 모델의 성능을 개선하기 위해 특성 선택(feature selection)이 필요할 때

한 문장 요약

PCA는 데이터의 차원을 줄이면서도 중요한 정보(변동성)를 최대한 유지하는 기법으로,
서로 상관성이 높은 축을 찾아 데이터를 변환(압축)하는 과정이다.

 

PCA의 단점 

1. 해석이 어렵다

  • PCA는 원래 변수들의 조합(선형 결합)으로 주성분을 생성한다.
  • 따라서 새로운 축(주성분)이 원래 데이터에서 어떤 의미를 가지는지 해석하기 어려울 수 있다.

2. 정보 손실 가능성

  • 차원을 줄이면서 일부 변동성이 낮은 데이터는 버려진다.
  • 이 과정에서 중요한 정보가 손실될 가능성이 있다.

3. 비선형 데이터에 부적합

  • PCA는 데이터의 선형 관계를 기반으로 동작한다.
  • 따라서 비선형적인 패턴을 가진 데이터에는 적절하지 않을 수 있다.

4. 스케일링(정규화) 필요

  • PCA는 변수들의 분산을 기반으로 하기 때문에 데이터의 크기(scale)가 다르면 제대로 동작하지 않는다.
  • 즉, 모든 변수를 같은 기준으로 맞추기 위해 반드시 표준화(Standardization)가 필요하다.

5. 차원 축소 후 원래 데이터 복원이 어려움

  • PCA를 사용하여 차원을 줄이면, 원래 데이터로 되돌리기 어렵다.
  • 즉, 데이터의 원본 형태를 유지하면서 축소하는 것이 아니라, 변형된 형태로 표현된다.

PCA는 데이터를 축소하는 강력한 도구이지만, 해석이 어렵고, 정보 손실과 비선형 데이터 처리 한계, 정규화 필요성, 원본 복원 어려움 등의 단점이 있다.

 

PCA의 단점을 보완하는 방법

1. 해석이 어려운 문제 → 주성분의 기여도를 분석

  • 해결 방법: 주성분이 원래 변수와 어떤 관계가 있는지 확인하려면, 주성분 로딩(loading) 행렬을 분석하면 된다.
  • 실행 코드:
    import pandas as pd
    pca_loadings = pd.DataFrame(pca.components_, columns=salary_df_numeric.columns)
    print(pca_loadings)
  • 효과: 각 주성분이 원래 변수에 미치는 영향을 파악할 수 있어 해석이 쉬워진다.

2. 정보 손실 문제 → 적절한 주성분 개수 선택

  • 해결 방법: 전체 분산의 90~95% 이상을 유지하는 최적의 주성분 개수(n_components)를 선택하면 된다.
  • 실행 코드:
    import matplotlib.pyplot as plt
    import numpy as np

    explained_variance_ratio = np.cumsum(pca.explained_variance_ratio_)
    plt.plot(range(1, len(explained_variance_ratio) + 1), explained_variance_ratio, marker='o')
    plt.xlabel('Number of Principal Components')
    plt.ylabel('Cumulative Explained Variance')
    plt.show()
  • 효과: 너무 적은 주성분을 선택하면 정보가 손실되므로, 적절한 개수를 선택해 손실을 최소화할 수 있다.

3. 비선형 데이터 처리 문제 → 커널 PCA(Kernel PCA) 사용

  • 해결 방법: 데이터가 비선형 관계를 가질 경우, Kernel PCA를 사용하면 비선형 패턴도 반영할 수 있다.
  • 실행 코드:
    from sklearn.decomposition import KernelPCA
    kpca = KernelPCA(n_components=2, kernel='rbf')  # RBF 커널 사용
    salary_df_kpca = kpca.fit_transform(salary_df_numeric)
  • 효과: 선형 PCA보다 더 복잡한 데이터 구조를 학습할 수 있어, 비선형 관계를 반영하는 데 유용하다.

4. 정규화(스케일링) 필요 문제 → PCA 적용 전 데이터 표준화

  • 해결 방법: StandardScaler를 사용하여 데이터를 평균 0, 분산 1로 정규화한 후 PCA를 적용하면 스케일 차이 문제를 해결할 수 있다.
  • 실행 코드:
    from sklearn.preprocessing import StandardScaler

    scaler = StandardScaler()
    salary_df_scaled = scaler.fit_transform(salary_df_numeric)

    pca = PCA(n_components=2)
    salary_df_pca = pca.fit_transform(salary_df_scaled)
  • 효과: 모든 변수의 중요도를 동일하게 만들어, 특정 변수가 과도한 영향을 주는 것을 방지할 수 있다.

5. 차원 축소 후 원본 데이터 복원 문제 → Inverse Transform 사용

  • 해결 방법: pca.inverse_transform()을 사용하면, 축소된 데이터를 원래 데이터 공간으로 복원할 수 있다.
  • 실행 코드:
    salary_df_approx = pca.inverse_transform(salary_df_pca)
  • 효과: 원본 데이터를 최대한 유지하면서 차원 축소된 데이터를 활용할 수 있다.

PCA의 단점은 주성분 기여도 분석(해석 문제 해결), 최적 주성분 개수 선택(정보 손실 방지), Kernel PCA(비선형 데이터 대응), 정규화(스케일 문제 해결), Inverse Transform(원본 복원 가능) 등으로 보완할 수 있다.

 

모델링 하는것도 중요하지만, 해석하는것도 중요하기때문에 PCA로 예측이 올라간다고 해도 해석하기에는 단점이 있으므로 본인의 선택 또는 위에 단점을 보완하는 방법을 사용해야 겠죠.


오류 메시지 AttributeError: 'PCA' object has no attribute 'mean_'는 PCA 객체가 제대로 학습되지 않았을 때 발생하는 오류이다.
즉, pca.fit()을 먼저 수행하지 않고 pca.transform()을 실행했기 때문에 발생한 문제이다.

해결 방법

PCA를 사용하려면 먼저 데이터를 학습(fit)한 후 변환(transform)해야 한다.

1. PCA 학습(fit) 먼저 수행

from sklearn.decomposition import PCA
import numpy as np

# 숫자형 데이터만 선택
salary_df_numeric = salary_df.select_dtypes(include=[np.number])

# PCA 객체 생성 및 학습
pca = PCA(n_components=2)  # 예시로 주성분 개수를 2개로 설정
pca.fit(salary_df_numeric)  # 데이터를 학습

# 변환 수행
pca_result = pca.transform(salary_df_numeric)
 

이제 pca.transform()이 정상적으로 실행될 것이다.

2. PCA 객체가 이미 학습되었는지 확인

PCA 객체가 제대로 학습되었는지 확인하려면 pca.mean_ 속성이 존재하는지 확인할 수 있다.

print(hasattr(pca, "mean_"))  # True가 나오면 fit이 정상적으로 실행된 것
 

만약 False가 나오면 pca.fit(salary_df_numeric)이 실행되지 않은 상태이므로 반드시 학습을 먼저 수행해야 한다.

오류의 원인은 PCA 객체가 데이터를 학습하지 않은 상태에서 transform을 실행했기 때문이다.
이를 해결하려면 먼저 pca.fit()을 실행한 후 pca.transform()을 수행해야 한다.


변수 중 Data Type이 object인 변수들 제거하고 저장하는 코드는 아래의 두가지경우인데 특징과 장단점

1. salary_df = salary_df.select_dtypes(exclude=['object'])

실행 결과

  • 문자형(object) 데이터 타입을 가진 모든 열을 제거.
  • 자동으로 모든 문자열 컬럼을 삭제하므로, 특정 컬럼을 지정할 필요 없음.
  • 예를 들어, 'Gender', 'Job Title', 'Country', 'Race' 외에도 다른 문자형 변수가 있으면 함께 제거됨.

장점

  • 데이터프레임에 있는 모든 문자형 변수를 자동으로 제거하므로 간편함.
  • 컬럼 이름을 일일이 지정할 필요 없음.
  • 예를 들어, 데이터에 "Department" 같은 새로운 문자형 컬럼이 추가되어도 자동으로 제거됨.

단점

  • 숫자형 변수 중에서 문자처럼 저장된 경우(object 타입으로 저장된 숫자)가 있다면 함께 삭제될 수 있음.
  • 특정한 문자형 변수만 선택적으로 제거하고 싶을 때는 불필요한 삭제가 발생할 수 있음.

2. salary_df.drop(['Gender','Job Title', 'Country', 'Race'], axis=1, inplace=True)

실행 결과

  • 명시적으로 지정한 컬럼만 삭제('Gender', 'Job Title', 'Country', 'Race').
  • 이외의 문자형 컬럼은 그대로 유지됨.

장점

  • 원하는 컬럼만 정확하게 삭제 가능.
  • object 타입이지만 **숫자로 변환할 가능성이 있는 컬럼(예: 'Salary'가 object로 저장된 경우)**은 유지 가능.
  • 데이터의 구조를 확실히 알고 있을 때 적절한 방식.

단점

  • 새로운 문자형 변수가 추가되면 다시 수동으로 삭제해야 함.
  • 특정 문자형 변수를 삭제하고 싶을 때마다 컬럼 리스트를 수정해야 하는 번거로움이 있음.
  • 모든 문자형 변수를 제거하려면 → select_dtypes(exclude=['object']) 사용.
  • 특정 문자형 변수만 제거하려면 → drop([...], axis=1, inplace=True) 사용.

 

 


코드별 목적

# salary_1 이름으로 salary_1.csv 불러오기 (데이터 위치: 'data/salary_1.csv')
salary_1 =  pd.read_csv('data/salary_1.csv')
 
# salary_2 이름으로 salary_2.csv 불러오기 (데이터 위치: 'data/salary_2.csv')
salary_2 =  pd.read_csv('data/salary_2.csv')
 
# salary_1 과 salary_2를 위/아래로 붙이고, salary_df 이름으로 저장하기
salary_df = pd.concat([salary_1, salary_2], axis=0).reset_index(drop=True)
 
# salary_df의 결측치 비율 확인하기
missing_ratio = salary_df.isnull().mean()
 
# 결측치 행 제거하기
salary_df =  salary_df.dropna()
 
# Gender별 Salary의 평균(mean)을 구해 gender_salary 로 저장하기
gender_salary =  salary_df.groupby('Gender')['Salary'].mean()
 
# gender_salary의 인덱스(Gender)를 컬럼으로 전환하여 저장하기
gender_salary =  gender_salary.reset_index()
 
# Gender를 기준으로 하여, salary_df에 gender_salary를 붙여서(left join) salary_df로 저장하기
salary_df =  salary_df.merge(gender_salary, on='Gender', how='left')
 
# 컬럼이름 변경: "Salary_x"를 "Salary"로, "Salary_y"를 "Gender_salary"로 변경하여 저장
salary_df =  salary_df.rename(columns={'Salary_x': 'Salary', 'Salary_y': 'Gender_salary'})
 
# 다음 기준으로 Pivot Table 만들기 -> 행: Country, 열: Gender, 값: Years of Experience
pivot_table = salary_df.pivot_table(index='Country', columns='Gender', values='Years of Experience')
 
# Salary 변수에 로그를 취하여 'Salary_log'로 저장하기
salary_df['Salary_log'] =  np.log(salary_df['Salary'])
 
# 변수 중 Data Type이 object인 변수들 제거하고 저장하기
salary_df = salary_df.select_dtypes(exclude=['object'])
 
# RobustScaler 패키지 불러오기
from sklearn.preprocessing import RobustScaler
 
# RobustScaler를 사용하기 위해 rs 이름으로 저장
rs =  RobustScaler()
 
# rs로 salary_df의 정보를 학습시키기
rs.fit(salary_df)
 
# 학습된 rs로 salary_df를 변환하여 rs_df로 저장하기
rs_df =  rs.transform(salary_df)
 
# rs_df를 Pandas DataFrame으로 변경하여 저장 (컬럼 이름도 기존 컬럼이름으로 채워넣기)
rs_df =  pd.DataFrame(rs.transform(salary_df), columns=salary_df.columns)
 
# MinMaxScaler 패키지 불러오기
from sklearn.preprocessing import MinMaxScaler
 
# MinMaxScaler를 사용하기 위해 mm 이름으로 저장
mm = MinMaxScaler()
 
# 한 줄의 코드로 salary_df를 mm으로 학습하고 변형하여 mm_df 로 저장하기
mm_df =  pd.DataFrame(mm.fit_transform(salary_df), columns=salary_df.columns)
 
# mm_df를 Pandas DataFrame으로 변경하여 저장 (컬럼 이름도 기존 컬럼이름으로 채워넣기)
mm_df =  pd.DataFrame(mm_df, columns=salary_df.columns)
 
# salary_df에서 주성분을 뽑을 경우, 최대로 뽑을 수 있는 주성분 개수는?
max_components = min(salary_df.shape[0], salary_df.shape[1])
 
# PCA 패키지 불러오기
from sklearn.decomposition import PCA
 
# PCA를 사용하기 위해 pca 이름으로 저장: 2개의 주성분을 뽑을 수 있도록 설정
pca =  PCA(n_components=2)
 
# pca로 salary_df를 학습 및 변환하여 pca_df로 저장
pca_df =  #[[your code]]
 
# pca_df를 Pandas DataFrame으로 변경하고, 각 컬럼이름을 PC1, PC2로 설정하여 pca_df로 저장
pca_df =  pd.DataFrame(pca.fit_transform(salary_df), columns=[f'PC{i+1}' for i in range(2)])
 
# 추출된 두개의 주성분으로 기존 데이터 정보의 얼마만큼을 설명할 수 있는지 확인하는 코드 작성
(pca.explained_variance_ratio_).sum()