EDA 라이브러리 사용법
numpy pandas matplob 조지기
- 데이터 전처리
좋은 데이터 분석을 위해서는 EDA 가 필수. Explotatory Data Analysis
데이터 예제
이 데이터는 721 포켓몬의 수, 이름, 타입(두개까지), 기본스텟(HP, 공격력, 방어력, 필살공격력, 필살방어력, 속도) 를 포함하고 있습니다.
이 data 는 카드 or 포켓몬 고 가 아니라 포켓몬 게임 data입니다.
관련 내용은 Descipt 에서 확인해 볼 수 있습니다.
정말 중요한거
당신이 데이터를 다루고 싶다면, 그게 어떤 데이터를 어떻게 담고 있는지는 무조건 확인을 해봐야 한다.
1. 열어나 보기
- 가장 첫 항에는 순서별 속성을 나타내 주고 있고 그 밑에는 데이터가 있는
csv 데이터이다
2. 라이브러리 준비하기
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import os
csv 데이터를 잘 다루기 위해 필요한 라이브러리이다.
- Numpy (배열)
- Pandas (행렬)
- Seaborn (시각화)
- Matplot (시각화)
- os (경로설정)
다른건 잘 아시겠지만 Seaborn 얘는 matplot 의 상위 버전,
matpolt 이 raw 그래프라면
Seaborn 은 Luxery 그래프
3. 데이터 복사하기
import os
csv_path = os.getenv("HOME") + "/aiffel/Study/15_/pokemon_eda/data/Pokemon.csv"
original_data = pd.read_csv(csv_path)
original_data
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
795 | 719 | Diancie | Rock | Fairy | 600 | 50 | 100 | 150 | 100 | 150 | 50 | 6 | True |
796 | 719 | DiancieMega Diancie | Rock | Fairy | 700 | 50 | 160 | 110 | 160 | 110 | 110 | 6 | True |
797 | 720 | HoopaHoopa Confined | Psychic | Ghost | 600 | 80 | 110 | 60 | 150 | 130 | 70 | 6 | True |
798 | 720 | HoopaHoopa Unbound | Psychic | Dark | 680 | 80 | 160 | 60 | 170 | 130 | 80 | 6 | True |
799 | 721 | Volcanion | Fire | Water | 600 | 80 | 110 | 120 | 130 | 90 | 70 | 6 | True |
800 rows × 13 columns
이 데이터에 훼손이 가면 안되니까 일단 복사를 해서 사용하자
pokemon = original_data.copy()
print(pokemon.shape)
pokemon.head()
(800, 13)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
pandas 는 shape 메서드로 행렬을 파악할 수 있다.
지금 보니 800개의 행(포켓몬 수), 13개의 열(포켓몬의 속성) 임을 알 수 있다.
우리가 확인할 것은 전설의 포켓몬이다. 자 해보자
legendary = pokemon[pokemon["Legendary"] == True].reset_index(drop=True)
print(legendary.shape)
legendary.head()
(65, 13)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 144 | Articuno | Ice | Flying | 580 | 90 | 85 | 100 | 95 | 125 | 85 | 1 | True |
1 | 145 | Zapdos | Electric | Flying | 580 | 90 | 90 | 85 | 125 | 90 | 100 | 1 | True |
2 | 146 | Moltres | Fire | Flying | 580 | 90 | 100 | 90 | 125 | 85 | 90 | 1 | True |
3 | 150 | Mewtwo | Psychic | NaN | 680 | 106 | 110 | 90 | 154 | 90 | 130 | 1 | True |
4 | 150 | MewtwoMega Mewtwo X | Psychic | Fighting | 780 | 106 | 190 | 100 | 154 | 100 | 130 | 1 | True |
reset_index 메서드(df전용메서드) 로 따로 떨궈 새 df를 만들었다
물론 일반몬만 모아논 것도 df로 만들거임
ordinary = pokemon[pokemon['Legendary'] == False].reset_index(drop=True)
print(ordinary.shape)
ordinary.head()
(735, 13)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
4. 데이터 손실 감안하기
결측치 파일이 있으면 통계나 eda 에 영향이 있으므로 이런 건 빼주어야 한다
pokemon.isnull().sum()
# 0
Name 0
Type 1 0
Type 2 386
Total 0
HP 0
Attack 0
Defense 0
Sp. Atk 0
Sp. Def 0
Speed 0
Generation 0
Legendary 0
dtype: int64
자 이걸 보니 다 데이터가 있긴 한데
포켓몬중에 type2 가 없는 애들이 있다.
당연하다 꼬부기 같은 애들은 물 그자체자너 이상할게 없다.
근데 문제는 이게 없는게 당연한건데 평균같은거 낼때
2번째 타입까지 묶어서 조사하면 물속성이 현저히 떨어진다 이러면
뭔가 잘못된거잖어 그지 그런걸 방지해야 한다.
print(len(pokemon.columns))
pokemon.columns
13
Index(['#', 'Name', 'Type 1', 'Type 2', 'Total', 'HP', 'Attack', 'Defense',
'Sp. Atk', 'Sp. Def', 'Speed', 'Generation', 'Legendary'],
dtype='object')
데이터 손실을 감안하기 위해선 각 데이터들의 속성(데이터타입) 을
꼼꼼히 봐야 한다.
name, type1, type2 는 str 타입, Legendary 는 boolean, 나머지는 int 타입이다.
지금부터 하나씩 뜯어보자.
5. 본격적인 EDA (데이터 분석)
5-1. 1열 ; ’#’
len(set(pokemon["#"]))
721
#은 아까 포켓몬 넘버링 한거였는데 800개의 행이 있는데도 721개밖에 잡히지 않는 걸 알 수 있다. 그렇다는 건 #이 중복되는 경우가 있다는 뜻. 한번 찾아보자
pokemon[pokemon["#"] == 6]
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 6 | Charizard | Fire | Flying | 534 | 78 | 84 | 78 | 109 | 85 | 100 | 1 | False |
7 | 6 | CharizardMega Charizard X | Fire | Dragon | 634 | 78 | 130 | 111 | 130 | 85 | 100 | 1 | False |
8 | 6 | CharizardMega Charizard Y | Fire | Flying | 634 | 78 | 104 | 78 | 159 | 115 | 100 | 1 | False |
보니 6번째 #이 3마리나 있다.차리자드 ? 뭐야
친근한 리자몽이었다. 메가리자몽의 남 녀 까지 총 3마리였던 것이다. 하나의 포켓몬이 여러 종류로 분화하는 경우는 이브이 등 여러 포켓몬이 존재한다.
5-2 2열 ; Name
len(set(pokemon["Name"]))
800
800개 깔끔히 있는 거 보니 중복데이터는 없는 모양이다.
5-3 3열과 4열 ; type
pokemon.loc[[6, 10]]
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 6 | Charizard | Fire | Flying | 534 | 78 | 84 | 78 | 109 | 85 | 100 | 1 | False |
10 | 8 | Wartortle | Water | NaN | 405 | 59 | 63 | 80 | 65 | 80 | 58 | 1 | False |
포켓몬은 하나부터 두개의 속성을가지고 있다.
Wartortle 을 보니 꼬부기인가? ㅋㅋㅋ 두번째 속성은 NaN 이라고 없다 뜬다.
len(set(pokemon['Type 1'])), len(set(pokemon['Type 2'])),
(18, 19)
타입 1, 2 에 다른 속성이 있는 것 같다. 둘의 차집합을 구해보자.
참고 : Set타입의 처리
pokemon.loc[[6, 10]]
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 6 | Charizard | Fire | Flying | 534 | 78 | 84 | 78 | 109 | 85 | 100 | 1 | False |
10 | 8 | Wartortle | Water | NaN | 405 | 59 | 63 | 80 | 65 | 80 | 58 | 1 | False |
set(pokemon["Type 2"]) - set(pokemon["Type 1"])
{nan}
아,, 아무 속성도 없는 NaN 이 type2 에 있었던 것이다.
그렇다면 적어도 모든 포켓몬은 하나의 속성을 가지고 있다.
types = list(set(pokemon["Type 1"]))
types
['Fighting',
'Psychic',
'Flying',
'Electric',
'Normal',
'Poison',
'Steel',
'Water',
'Fairy',
'Ice',
'Ghost',
'Ground',
'Dark',
'Grass',
'Fire',
'Bug',
'Dragon',
'Rock']
하나의 타입만 가지고 있는 사람,,아니 포켓몬은 type2 가 nan 이기 때문에 역으로 구할 수가 있습니다.
pokemon[pokemon['Type 2'] == 'NaN']
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary |
---|
근데 이렇게 하니까 안뜨네요??? 포인트는, NaN 데이터는 str 데이터가 아니라 말그대로 None 데이터를 의미합니다
그래서 pandas 에서 제공하는 isna() 메서드를 따로 사용해야 합니다/
pokemon["Type 2"].isna().sum()
386
386마리의 포켓몬스터가 하나의 속성을 가지고 있군요
pokemon["Type 2"].isna()
0 False
1 False
2 False
3 False
4 True
...
795 False
796 False
797 False
798 False
799 False
Name: Type 2, Length: 800, dtype: bool
보시는 것처럼 isnaa() 메서드는 None 타입 을 찾아 True 를 반환해줍니다.
5. 데이터 시각화 (이것도 EDA의 일종)
Type 1 의 경우
plt.figure(figsize=(10, 7)) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
plt.subplot(211)
sns.countplot(data=ordinary, x="Type 1", order=types).set_xlabel('')
plt.title("[Ordinary Pokemons]")
plt.subplot(212)
sns.countplot(data=legendary, x="Type 1", order=types).set_xlabel('')
plt.title("[Legendary Pokemons]")
plt.show()
counplot(data=‘a’.x=‘b’,order=‘c’)
a : 우리가 계산할 데이터자료
b : 데이터 자료 중 찾고싶은 형식
c : 구분할 서브 데이터자료
# Type1별로 Legendary 의 비율을 보여주는 피벗 테이블
pd.pivot_table(pokemon,
index="Type 1",
values="Legendary").sort_values(by=["Legendary"], ascending=False)
Legendary | |
---|---|
Type 1 | |
Flying | 0.500000 |
Dragon | 0.375000 |
Psychic | 0.245614 |
Steel | 0.148148 |
Ground | 0.125000 |
Fire | 0.096154 |
Electric | 0.090909 |
Rock | 0.090909 |
Ice | 0.083333 |
Dark | 0.064516 |
Ghost | 0.062500 |
Fairy | 0.058824 |
Grass | 0.042857 |
Water | 0.035714 |
Normal | 0.020408 |
Poison | 0.000000 |
Fighting | 0.000000 |
Bug | 0.000000 |
Type 2 의 경우
plt.figure(figsize=(12, 10)) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
plt.subplot(211)
sns.countplot(data=ordinary, x="Type 2", order=types).set_xlabel('')
plt.title("[Ordinary Pokemons]")
plt.subplot(212)
sns.countplot(data=legendary, x="Type 2", order=types).set_xlabel('')
plt.title("[Legendary Pokemons]")
plt.show()
# Type2별로 Legendary 의 비율을 보여주는 피벗 테이블
pd.pivot_table(pokemon,
index="Type 2",
values="Legendary").sort_values(by=["Legendary"], ascending=False)
Legendary | |
---|---|
Type 2 | |
Fire | 0.250000 |
Dragon | 0.222222 |
Ice | 0.214286 |
Electric | 0.166667 |
Fighting | 0.153846 |
Psychic | 0.151515 |
Flying | 0.134021 |
Fairy | 0.086957 |
Water | 0.071429 |
Ghost | 0.071429 |
Dark | 0.050000 |
Steel | 0.045455 |
Ground | 0.028571 |
Rock | 0.000000 |
Bug | 0.000000 |
Poison | 0.000000 |
Normal | 0.000000 |
Grass | 0.000000 |
모든 스텝의 총합
stats = ["HP", "Attack", "Defense", "Sp. Atk", "Sp. Def", "Speed"]
stats
['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']
print("#0 pokemon: ", pokemon.loc[0, "Name"])
print("total: ", int(pokemon.loc[0, "Total"]))
print("stats: ", list(pokemon.loc[0, stats]))
print("sum of all stats: ", sum(list(pokemon.loc[0, stats])))
#0 pokemon: Bulbasaur
total: 318
stats: [45, 49, 49, 65, 65, 45]
sum of all stats: 318
- 참고로 df을 이용계산해주기 위해선 int나 list 데이터 형식으로 변환해줘야 한다..
pokemon.loc[0]
# 1
Name Bulbasaur
Type 1 Grass
Type 2 Poison
Total 318
HP 45
Attack 49
Defense 49
Sp. Atk 65
Sp. Def 65
Speed 45
Generation 1
Legendary False
Name: 0, dtype: object
list(pokemon.loc[0])
[1, 'Bulbasaur', 'Grass', 'Poison', 318, 45, 49, 49, 65, 65, 45, 1, False]
짜잔~ dic도 가능하다
dict(pokemon.loc[0])
{'#': 1,
'Name': 'Bulbasaur',
'Type 1': 'Grass',
'Type 2': 'Poison',
'Total': 318,
'HP': 45,
'Attack': 49,
'Defense': 49,
'Sp. Atk': 65,
'Sp. Def': 65,
'Speed': 45,
'Generation': 1,
'Legendary': False}
이걸 바탕으로 모든 포켓몬들의 스텟 합친게 TOTAL 인지 확인해보자.
sum(pokemon['Total'].values == pokemon[stats].values.sum(axis=1))
800
- 이런게 진짜 중요 보면 STAT은 세로로 더해야 함으로 AXIS=1 을 둔 것이다
pokemon['Total'].values
array([318, 405, 525, 625, 309, 405, 534, 634, 634, 314, 405, 530, 630,
195, 205, 395, 195, 205, 395, 495, 251, 349, 479, 579, 253, 413,
262, 442, 288, 438, 320, 485, 300, 450, 275, 365, 505, 273, 365,
505, 323, 483, 299, 505, 270, 435, 245, 455, 320, 395, 490, 285,
405, 305, 450, 265, 405, 290, 440, 320, 500, 305, 455, 350, 555,
300, 385, 510, 310, 400, 500, 590, 305, 405, 505, 300, 390, 490,
335, 515, 300, 390, 495, 410, 500, 315, 490, 590, 325, 465, 352,
310, 460, 325, 475, 325, 500, 305, 525, 310, 405, 500, 600, 385,
328, 483, 325, 475, 330, 480, 325, 520, 320, 425, 455, 455, 385,
340, 490, 345, 485, 450, 435, 490, 590, 295, 440, 320, 450, 340,
520, 460, 500, 455, 490, 495, 500, 600, 490, 200, 540, 640, 535,
288, 325, 525, 525, 525, 395, 355, 495, 355, 495, 515, 615, 540,
580, 580, 580, 300, 420, 600, 680, 780, 780, 600, 318, 405, 525,
309, 405, 534, 314, 405, 530, 215, 415, 262, 442, 265, 390, 250,
390, 535, 330, 460, 205, 218, 210, 245, 405, 320, 470, 280, 365,
510, 610, 490, 250, 420, 410, 500, 250, 340, 460, 360, 180, 425,
390, 210, 430, 525, 525, 405, 490, 435, 336, 405, 455, 290, 465,
415, 430, 510, 610, 300, 450, 430, 500, 600, 505, 500, 600, 430,
330, 500, 250, 410, 250, 450, 380, 300, 480, 330, 465, 465, 330,
500, 600, 540, 330, 500, 515, 465, 250, 210, 455, 305, 360, 365,
490, 540, 580, 580, 580, 300, 410, 600, 700, 680, 680, 600, 310,
405, 530, 630, 310, 405, 530, 630, 310, 405, 535, 635, 220, 420,
240, 420, 195, 205, 395, 205, 385, 220, 340, 480, 220, 340, 480,
270, 430, 270, 430, 198, 278, 518, 618, 269, 414, 295, 460, 280,
440, 670, 266, 456, 236, 240, 360, 490, 237, 474, 190, 375, 260,
380, 380, 480, 380, 480, 330, 430, 530, 630, 280, 410, 510, 295,
475, 575, 405, 405, 400, 400, 400, 302, 467, 305, 460, 560, 400,
500, 305, 460, 560, 470, 330, 470, 360, 290, 340, 520, 335, 475,
310, 490, 590, 458, 458, 440, 440, 288, 468, 308, 468, 300, 500,
355, 495, 355, 495, 200, 540, 420, 440, 295, 455, 555, 295, 455,
460, 425, 465, 565, 260, 300, 480, 580, 290, 410, 530, 345, 485,
485, 485, 330, 300, 420, 600, 700, 300, 420, 600, 700, 580, 580,
580, 600, 700, 600, 700, 670, 770, 670, 770, 680, 780, 600, 600,
600, 600, 600, 318, 405, 525, 309, 405, 534, 314, 405, 530, 245,
340, 485, 250, 410, 194, 384, 263, 363, 523, 280, 515, 350, 495,
350, 495, 224, 424, 424, 424, 424, 244, 474, 405, 330, 495, 275,
450, 325, 475, 482, 348, 498, 350, 480, 580, 495, 505, 310, 452,
285, 329, 479, 300, 500, 290, 310, 220, 411, 485, 300, 410, 600,
700, 390, 285, 525, 625, 330, 525, 330, 500, 300, 490, 454, 330,
460, 345, 334, 494, 594, 510, 535, 515, 535, 535, 540, 540, 545,
515, 525, 525, 510, 530, 535, 518, 618, 525, 525, 480, 440, 520,
520, 520, 520, 520, 580, 580, 580, 680, 680, 600, 670, 680, 680,
600, 480, 600, 600, 600, 600, 720, 600, 308, 413, 528, 308, 418,
528, 308, 413, 528, 255, 420, 275, 370, 500, 281, 446, 316, 498,
316, 498, 316, 498, 292, 487, 264, 358, 488, 295, 497, 280, 390,
515, 313, 425, 328, 508, 445, 545, 305, 405, 505, 294, 384, 509,
465, 465, 310, 380, 500, 260, 360, 485, 280, 480, 280, 480, 460,
292, 351, 519, 315, 480, 540, 461, 325, 475, 348, 488, 490, 303,
483, 355, 495, 401, 567, 329, 474, 330, 510, 300, 470, 290, 390,
490, 290, 370, 490, 305, 473, 305, 395, 535, 335, 475, 428, 315,
495, 294, 464, 335, 480, 470, 319, 472, 305, 489, 300, 440, 520,
275, 405, 515, 335, 485, 275, 370, 520, 320, 410, 540, 305, 485,
485, 305, 495, 471, 350, 510, 485, 303, 483, 340, 490, 490, 350,
510, 370, 510, 484, 484, 300, 420, 600, 360, 550, 580, 580, 580,
580, 580, 580, 580, 680, 680, 600, 600, 660, 700, 700, 580, 580,
600, 600, 600, 313, 405, 530, 307, 409, 534, 314, 405, 530, 237,
423, 278, 382, 499, 200, 213, 411, 369, 507, 303, 371, 552, 350,
531, 348, 495, 472, 355, 466, 466, 325, 448, 520, 520, 341, 462,
341, 480, 288, 482, 306, 500, 320, 494, 330, 500, 289, 481, 362,
521, 362, 521, 525, 500, 431, 500, 300, 452, 600, 470, 309, 474,
335, 335, 335, 335, 494, 494, 494, 494, 304, 514, 245, 535, 680,
680, 600, 600, 700, 600, 680, 600])
pokemon[stats].values
array([[ 45, 49, 49, 65, 65, 45],
[ 60, 62, 63, 80, 80, 60],
[ 80, 82, 83, 100, 100, 80],
...,
[ 80, 110, 60, 150, 130, 70],
[ 80, 160, 60, 170, 130, 80],
[ 80, 110, 120, 130, 90, 70]])
신기하지, 나도 그래
자그럼 TOTAL 값과 레전드몬스터들은 연관성이 있을까?
fig, ax = plt.subplots()
fig.set_size_inches(12, 6) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
sns.scatterplot(data=pokemon, x="Type 1", y="Total", hue="Legendary")
plt.show()
보면 hue 는 레전드몬스터들을 노란색으로 표시해준거다
토탈이 높을수록 레전드네? ㄴㅇㅅ 연관성 있는거 확인했다
figure, ((ax1, ax2), (ax3, ax4), (ax5, ax6)) = plt.subplots(nrows=3, ncols=2)
figure.set_size_inches(12, 18) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
sns.scatterplot(data=pokemon, y="Total", x="HP", hue="Legendary", ax=ax1)
sns.scatterplot(data=pokemon, y="Total", x="Attack", hue="Legendary", ax=ax2)
sns.scatterplot(data=pokemon, y="Total", x="Defense", hue="Legendary", ax=ax3)
sns.scatterplot(data=pokemon, y="Total", x="Sp. Atk", hue="Legendary", ax=ax4)
sns.scatterplot(data=pokemon, y="Total", x="Sp. Def", hue="Legendary", ax=ax5)
sns.scatterplot(data=pokemon, y="Total", x="Speed", hue="Legendary", ax=ax6)
plt.show()
세대 확인
plt.figure(figsize=(12, 10)) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
plt.subplot(211)
sns.countplot(data=ordinary, x="Generation").set_xlabel('')
plt.title("[All Pkemons]")
plt.subplot(212)
sns.countplot(data=legendary, x="Generation").set_xlabel('')
plt.title("[Legendary Pkemons]")
plt.show()
Total 값 비교
fig, ax = plt.subplots()
fig.set_size_inches(8, 4)
sns.scatterplot(data=legendary, y="Type 1", x="Total")
plt.show()
print(sorted(list(set(legendary["Total"]))))
[580, 600, 660, 670, 680, 700, 720, 770, 780]
fig, ax = plt.subplots()
fig.set_size_inches(8, 4)
sns.countplot(data=legendary, x="Total")
plt.show()
전설의 포켓몬이 65마리나 되는데 9개의 stat을 공유하고 있다.
이건 약 7.2마리의 포켓몬이 같은 stat이라는 뜻이다
일반 포켓몬은 그렇지 않으므로 여기서 하나의 특이사항을 도출 할 수 있습니다
- Total값 집합은 전설의 포켓몬인지에 영향을 끼친다.
- Total 값이 높을수록 전설의 포켓몬일 확률이 크다.
이름 확인
n1, n2, n3, n4, n5 = legendary[3:6], legendary[14:24], legendary[25:29], legendary[46:50], legendary[52:57]
names = pd.concat([n1, n2, n3, n4, n5]).reset_index(drop=True)
names
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 150 | Mewtwo | Psychic | NaN | 680 | 106 | 110 | 90 | 154 | 90 | 130 | 1 | True |
1 | 150 | MewtwoMega Mewtwo X | Psychic | Fighting | 780 | 106 | 190 | 100 | 154 | 100 | 130 | 1 | True |
2 | 150 | MewtwoMega Mewtwo Y | Psychic | NaN | 780 | 106 | 150 | 70 | 194 | 120 | 140 | 1 | True |
3 | 380 | Latias | Dragon | Psychic | 600 | 80 | 80 | 90 | 110 | 130 | 110 | 3 | True |
4 | 380 | LatiasMega Latias | Dragon | Psychic | 700 | 80 | 100 | 120 | 140 | 150 | 110 | 3 | True |
5 | 381 | Latios | Dragon | Psychic | 600 | 80 | 90 | 80 | 130 | 110 | 110 | 3 | True |
6 | 381 | LatiosMega Latios | Dragon | Psychic | 700 | 80 | 130 | 100 | 160 | 120 | 110 | 3 | True |
7 | 382 | Kyogre | Water | NaN | 670 | 100 | 100 | 90 | 150 | 140 | 90 | 3 | True |
8 | 382 | KyogrePrimal Kyogre | Water | NaN | 770 | 100 | 150 | 90 | 180 | 160 | 90 | 3 | True |
9 | 383 | Groudon | Ground | NaN | 670 | 100 | 150 | 140 | 100 | 90 | 90 | 3 | True |
10 | 383 | GroudonPrimal Groudon | Ground | Fire | 770 | 100 | 180 | 160 | 150 | 90 | 90 | 3 | True |
11 | 384 | Rayquaza | Dragon | Flying | 680 | 105 | 150 | 90 | 150 | 90 | 95 | 3 | True |
12 | 384 | RayquazaMega Rayquaza | Dragon | Flying | 780 | 105 | 180 | 100 | 180 | 100 | 115 | 3 | True |
13 | 386 | DeoxysNormal Forme | Psychic | NaN | 600 | 50 | 150 | 50 | 150 | 50 | 150 | 3 | True |
14 | 386 | DeoxysAttack Forme | Psychic | NaN | 600 | 50 | 180 | 20 | 180 | 20 | 150 | 3 | True |
15 | 386 | DeoxysDefense Forme | Psychic | NaN | 600 | 50 | 70 | 160 | 70 | 160 | 90 | 3 | True |
16 | 386 | DeoxysSpeed Forme | Psychic | NaN | 600 | 50 | 95 | 90 | 95 | 90 | 180 | 3 | True |
17 | 641 | TornadusIncarnate Forme | Flying | NaN | 580 | 79 | 115 | 70 | 125 | 80 | 111 | 5 | True |
18 | 641 | TornadusTherian Forme | Flying | NaN | 580 | 79 | 100 | 80 | 110 | 90 | 121 | 5 | True |
19 | 642 | ThundurusIncarnate Forme | Electric | Flying | 580 | 79 | 115 | 70 | 125 | 80 | 111 | 5 | True |
20 | 642 | ThundurusTherian Forme | Electric | Flying | 580 | 79 | 105 | 70 | 145 | 80 | 101 | 5 | True |
21 | 645 | LandorusIncarnate Forme | Ground | Flying | 600 | 89 | 125 | 90 | 115 | 80 | 101 | 5 | True |
22 | 645 | LandorusTherian Forme | Ground | Flying | 600 | 89 | 145 | 90 | 105 | 80 | 91 | 5 | True |
23 | 646 | Kyurem | Dragon | Ice | 660 | 125 | 130 | 90 | 130 | 90 | 95 | 5 | True |
24 | 646 | KyuremBlack Kyurem | Dragon | Ice | 700 | 125 | 170 | 100 | 120 | 90 | 95 | 5 | True |
25 | 646 | KyuremWhite Kyurem | Dragon | Ice | 700 | 125 | 120 | 90 | 170 | 100 | 95 | 5 | True |
비슷한 이름을 가진 것 끼리 꺼냈다 (그냥 보고 꺼냈다) 그다음 합쳤다.
[concat 에 대한 설명(https://rfriend.tistory.com/256)
formes = names[13:23]
formes
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
13 | 386 | DeoxysNormal Forme | Psychic | NaN | 600 | 50 | 150 | 50 | 150 | 50 | 150 | 3 | True |
14 | 386 | DeoxysAttack Forme | Psychic | NaN | 600 | 50 | 180 | 20 | 180 | 20 | 150 | 3 | True |
15 | 386 | DeoxysDefense Forme | Psychic | NaN | 600 | 50 | 70 | 160 | 70 | 160 | 90 | 3 | True |
16 | 386 | DeoxysSpeed Forme | Psychic | NaN | 600 | 50 | 95 | 90 | 95 | 90 | 180 | 3 | True |
17 | 641 | TornadusIncarnate Forme | Flying | NaN | 580 | 79 | 115 | 70 | 125 | 80 | 111 | 5 | True |
18 | 641 | TornadusTherian Forme | Flying | NaN | 580 | 79 | 100 | 80 | 110 | 90 | 121 | 5 | True |
19 | 642 | ThundurusIncarnate Forme | Electric | Flying | 580 | 79 | 115 | 70 | 125 | 80 | 111 | 5 | True |
20 | 642 | ThundurusTherian Forme | Electric | Flying | 580 | 79 | 105 | 70 | 145 | 80 | 101 | 5 | True |
21 | 645 | LandorusIncarnate Forme | Ground | Flying | 600 | 89 | 125 | 90 | 115 | 80 | 101 | 5 | True |
22 | 645 | LandorusTherian Forme | Ground | Flying | 600 | 89 | 145 | 90 | 105 | 80 | 91 | 5 | True |
저안에 속한 이름이라면 전설일 확률이 높다
legendary["name_count"] = legendary["Name"].apply(lambda i: len(i))
legendary.head()
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 144 | Articuno | Ice | Flying | 580 | 90 | 85 | 100 | 95 | 125 | 85 | 1 | True | 8 |
1 | 145 | Zapdos | Electric | Flying | 580 | 90 | 90 | 85 | 125 | 90 | 100 | 1 | True | 6 |
2 | 146 | Moltres | Fire | Flying | 580 | 90 | 100 | 90 | 125 | 85 | 90 | 1 | True | 7 |
3 | 150 | Mewtwo | Psychic | NaN | 680 | 106 | 110 | 90 | 154 | 90 | 130 | 1 | True | 6 |
4 | 150 | MewtwoMega Mewtwo X | Psychic | Fighting | 780 | 106 | 190 | 100 | 154 | 100 | 130 | 1 | True | 19 |
ordinary["name_count"] = ordinary["Name"].apply(lambda i: len(i))
ordinary.head()
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False | 9 |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False | 7 |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False | 8 |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False | 21 |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False | 10 |
이름의 길이에 따라 새로운 열을 만들었는데, 이것은 어떤 연관성이 있을까?
분포도로 만들어봤다
plt.figure(figsize=(12, 10)) # 화면 해상도에 따라 그래프 크기를 조정해 주세요.
plt.subplot(211)
sns.countplot(data=legendary, x="name_count").set_xlabel('')
plt.title("Legendary")
plt.subplot(212)
sns.countplot(data=ordinary, x="name_count").set_xlabel('')
plt.title("Ordinary")
plt.show()
15자가 넘어가면 레전드일 확률이 높아진다!
print(round(len(legendary[legendary["name_count"] > 9]) / len(legendary) * 100, 2), "%")
41.54 %
전설의 포켓몬 안에서도 이름이 10자리가 넘을 확률은 41% 이다
print(round(len(ordinary[ordinary["name_count"] > 9]) / len(ordinary) * 100, 2), "%")
15.65 %
확실히 일반몬들이랑 차이가 있다
- 만약 Latios 가 전설의 포켓몬 성이면, ’%%% Latios’ 는 전설일 확률이 높다
- 높은 확률의 전설 가문 성이 존재한다.
- 긴 이름이면 전설 확률이 높다
6 데이터 전처리하기
앞서 데이터를 요리조리 씹고뜯고 맛보고 즐기며 어느정도의 시사점을 발견했다.
1. 이름의 길이를 나타내는 열을 만들고 이름이 긴지 아닌지 판별하자 (10개 넘음 긴거)
pokemon["name_count"] = pokemon["Name"].apply(lambda i: len(i))
pokemon["long_name"] = pokemon["name_count"] >= 10
pokemon.head()
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | long_name | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False | 9 | False |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False | 7 | False |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False | 8 | False |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False | 21 | True |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False | 10 | True |
2. 이름에 따라 전설일 확률이 높으니 전설의 이름들을 추출하자
근데 이걸 하기 위해선 먼저 포켓몬의 이름을 분류해야 한다.
이름만 있는 경우, 숫자 특수문자가 섞여있는 경우 등 획일화 되지 않았기에 전처리를 해주어야 한다.
pokemon["Name_nospace"] = pokemon["Name"].apply(lambda i: i.replace(" ", ""))
pokemon["name_isalpha"] = pokemon["Name_nospace"].apply(lambda i: i.isalpha())
#공백을 먼저 없애준 건 isalpha 메서드가 공백도 false로 멍청하게 생각해서 그거 막아줄라구
print(pokemon[pokemon["name_isalpha"] == False].shape)
pokemon[pokemon["name_isalpha"] == False]
(9, 17)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | long_name | Name_nospace | name_isalpha | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
34 | 29 | Nidoran♀ | Poison | NaN | 275 | 55 | 47 | 52 | 40 | 40 | 41 | 1 | False | 8 | False | Nidoran♀ | False |
37 | 32 | Nidoran♂ | Poison | NaN | 273 | 46 | 57 | 40 | 40 | 40 | 50 | 1 | False | 8 | False | Nidoran♂ | False |
90 | 83 | Farfetch'd | Normal | Flying | 352 | 52 | 65 | 55 | 58 | 62 | 60 | 1 | False | 10 | True | Farfetch'd | False |
131 | 122 | Mr. Mime | Psychic | Fairy | 460 | 40 | 45 | 65 | 100 | 120 | 90 | 1 | False | 8 | False | Mr.Mime | False |
252 | 233 | Porygon2 | Normal | NaN | 515 | 85 | 80 | 90 | 105 | 95 | 60 | 2 | False | 8 | False | Porygon2 | False |
270 | 250 | Ho-oh | Fire | Flying | 680 | 106 | 130 | 90 | 110 | 154 | 90 | 2 | True | 5 | False | Ho-oh | False |
487 | 439 | Mime Jr. | Psychic | Fairy | 310 | 20 | 25 | 45 | 70 | 90 | 60 | 4 | False | 8 | False | MimeJr. | False |
525 | 474 | Porygon-Z | Normal | NaN | 535 | 85 | 80 | 70 | 135 | 75 | 90 | 4 | False | 9 | False | Porygon-Z | False |
794 | 718 | Zygarde50% Forme | Dragon | Ground | 600 | 108 | 100 | 121 | 81 | 95 | 95 | 6 | True | 16 | True | Zygarde50%Forme | False |
저바저바 특수문자 많은거 바바 9마리나 있다
내가 이래서 걸러준거다
9마리밖에 없으니까 이름 수정해주자 후,,
pokemon = pokemon.replace(to_replace="Nidoran♀", value="Nidoran X")
pokemon = pokemon.replace(to_replace="Nidoran♂", value="Nidoran Y")
pokemon = pokemon.replace(to_replace="Farfetch'd", value="Farfetchd")
pokemon = pokemon.replace(to_replace="Mr. Mime", value="Mr Mime")
pokemon = pokemon.replace(to_replace="Porygon2", value="Porygon")
pokemon = pokemon.replace(to_replace="Ho-oh", value="Ho Oh")
pokemon = pokemon.replace(to_replace="Mime Jr.", value="Mime Jr")
pokemon = pokemon.replace(to_replace="Porygon-Z", value="Porygon Z")
pokemon = pokemon.replace(to_replace="Zygarde50% Forme", value="Zygarde Forme")
pokemon.loc[[34, 37, 90, 131, 252, 270, 487, 525, 794]]
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | long_name | Name_nospace | name_isalpha | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
34 | 29 | Nidoran X | Poison | NaN | 275 | 55 | 47 | 52 | 40 | 40 | 41 | 1 | False | 8 | False | Nidoran X | False |
37 | 32 | Nidoran Y | Poison | NaN | 273 | 46 | 57 | 40 | 40 | 40 | 50 | 1 | False | 8 | False | Nidoran Y | False |
90 | 83 | Farfetchd | Normal | Flying | 352 | 52 | 65 | 55 | 58 | 62 | 60 | 1 | False | 10 | True | Farfetchd | False |
131 | 122 | Mr Mime | Psychic | Fairy | 460 | 40 | 45 | 65 | 100 | 120 | 90 | 1 | False | 8 | False | Mr.Mime | False |
252 | 233 | Porygon | Normal | NaN | 515 | 85 | 80 | 90 | 105 | 95 | 60 | 2 | False | 8 | False | Porygon | False |
270 | 250 | Ho Oh | Fire | Flying | 680 | 106 | 130 | 90 | 110 | 154 | 90 | 2 | True | 5 | False | Ho Oh | False |
487 | 439 | Mime Jr | Psychic | Fairy | 310 | 20 | 25 | 45 | 70 | 90 | 60 | 4 | False | 8 | False | MimeJr. | False |
525 | 474 | Porygon Z | Normal | NaN | 535 | 85 | 80 | 70 | 135 | 75 | 90 | 4 | False | 9 | False | Porygon Z | False |
794 | 718 | Zygarde Forme | Dragon | Ground | 600 | 108 | 100 | 121 | 81 | 95 | 95 | 6 | True | 16 | True | Zygarde50%Forme | False |
pokemon["Name_nospace"] = pokemon["Name"].apply(lambda i: i.replace(" ", ""))
pokemon["name_isalpha"] = pokemon["Name_nospace"].apply(lambda i: i.isalpha())
pokemon[pokemon["name_isalpha"] == False]
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | name_count | long_name | Name_nospace | name_isalpha |
---|
자 일단 하나 끝
획일화 된 상태에서 이제 이름은
- 한단어
- 두단어
- 두단어 뒤에 xy가 붙는경우
이렇게 나뉜다.
이 문자열들을 이제 처리해줄건데, 이때 꼭 필요한게 바로 re 정규표현식…!!
이거 진짜 왕중요하다 우왕중요하다 정말
import re
name = "CharizardMega Charizard X" #이런걸 적절히 쪼개서 리스트에 넣을거임
name.split()
['CharizardMega', 'Charizard', 'X']
temp = name.split()[0]
tokens = re.findall('[A-Z][a-z]*', temp)
tokens
['Charizard', 'Mega']
def tokenize(name):
name_split = name.split(" ")
tokens = []
for part_name in name_split:
a = re.findall('[A-Z][a-z]*', part_name)
tokens.extend(a)
return np.array(tokens)
정규표현식을 사용해 이름을 split 해서 공백과 대문자를 기준으로 리스트를 만든다음 배열 형식으로 반환하는 함수를 만들었다.
all_tokens = list(legendary["Name"].apply(tokenize).values)
token_set = []
for token in all_tokens:
token_set.extend(token)
print(len(set(token_set)))
print(token_set)
65
['Articuno', 'Zapdos', 'Moltres', 'Mewtwo', 'Mewtwo', 'Mega', 'Mewtwo', 'X', 'Mewtwo', 'Mega', 'Mewtwo', 'Y', 'Raikou', 'Entei', 'Suicune', 'Lugia', 'Ho', 'Regirock', 'Regice', 'Registeel', 'Latias', 'Latias', 'Mega', 'Latias', 'Latios', 'Latios', 'Mega', 'Latios', 'Kyogre', 'Kyogre', 'Primal', 'Kyogre', 'Groudon', 'Groudon', 'Primal', 'Groudon', 'Rayquaza', 'Rayquaza', 'Mega', 'Rayquaza', 'Jirachi', 'Deoxys', 'Normal', 'Forme', 'Deoxys', 'Attack', 'Forme', 'Deoxys', 'Defense', 'Forme', 'Deoxys', 'Speed', 'Forme', 'Uxie', 'Mesprit', 'Azelf', 'Dialga', 'Palkia', 'Heatran', 'Regigigas', 'Giratina', 'Altered', 'Forme', 'Giratina', 'Origin', 'Forme', 'Darkrai', 'Shaymin', 'Land', 'Forme', 'Shaymin', 'Sky', 'Forme', 'Arceus', 'Victini', 'Cobalion', 'Terrakion', 'Virizion', 'Tornadus', 'Incarnate', 'Forme', 'Tornadus', 'Therian', 'Forme', 'Thundurus', 'Incarnate', 'Forme', 'Thundurus', 'Therian', 'Forme', 'Reshiram', 'Zekrom', 'Landorus', 'Incarnate', 'Forme', 'Landorus', 'Therian', 'Forme', 'Kyurem', 'Kyurem', 'Black', 'Kyurem', 'Kyurem', 'White', 'Kyurem', 'Xerneas', 'Yveltal', 'Zygarde', 'Forme', 'Diancie', 'Diancie', 'Mega', 'Diancie', 'Hoopa', 'Hoopa', 'Confined', 'Hoopa', 'Hoopa', 'Unbound', 'Volcanion']
중복제외 총 65개의 이름이 검출.
from collections import Counter
most_common = Counter(token_set).most_common(10)
most_common
[('Forme', 15),
('Mega', 6),
('Mewtwo', 5),
('Kyurem', 5),
('Deoxys', 4),
('Hoopa', 4),
('Latias', 3),
('Latios', 3),
('Kyogre', 3),
('Groudon', 3)]
이렇게 counter 함수를 이용해 몇번 쓰였는지도 알 수 있었다.
for token, _ in most_common:
# pokemon[token] = ... 형식으로 사용하면 뒤에서 warning이 발생합니다
pokemon[f"{token}"] = pokemon["Name"].str.contains(token)
pokemon.head(10)
# | Name | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | ... | Forme | Mega | Mewtwo | Kyurem | Deoxys | Hoopa | Latias | Latios | Kyogre | Groudon | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | ... | False | False | False | False | False | False | False | False | False | False |
1 | 2 | Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | ... | False | False | False | False | False | False | False | False | False | False |
2 | 3 | Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | ... | False | False | False | False | False | False | False | False | False | False |
3 | 3 | VenusaurMega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | ... | False | True | False | False | False | False | False | False | False | False |
4 | 4 | Charmander | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | ... | False | False | False | False | False | False | False | False | False | False |
5 | 5 | Charmeleon | Fire | NaN | 405 | 58 | 64 | 58 | 80 | 65 | ... | False | False | False | False | False | False | False | False | False | False |
6 | 6 | Charizard | Fire | Flying | 534 | 78 | 84 | 78 | 109 | 85 | ... | False | False | False | False | False | False | False | False | False | False |
7 | 6 | CharizardMega Charizard X | Fire | Dragon | 634 | 78 | 130 | 111 | 130 | 85 | ... | False | True | False | False | False | False | False | False | False | False |
8 | 6 | CharizardMega Charizard Y | Fire | Flying | 634 | 78 | 104 | 78 | 159 | 115 | ... | False | True | False | False | False | False | False | False | False | False |
9 | 7 | Squirtle | Water | NaN | 314 | 44 | 48 | 65 | 50 | 64 | ... | False | False | False | False | False | False | False | False | False | False |
10 rows × 27 columns
for t in types:
pokemon[t] = (pokemon["Type 1"] == t) | (pokemon["Type 2"] == t)
pokemon[[["Type 1", "Type 2"] + types][0]].head()
Type 1 | Type 2 | Fighting | Psychic | Flying | Electric | Normal | Poison | Steel | Water | Fairy | Ice | Ghost | Ground | Dark | Grass | Fire | Bug | Dragon | Rock | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Grass | Poison | False | False | False | False | False | True | False | False | False | False | False | False | False | True | False | False | False | False |
1 | Grass | Poison | False | False | False | False | False | True | False | False | False | False | False | False | False | True | False | False | False | False |
2 | Grass | Poison | False | False | False | False | False | True | False | False | False | False | False | False | False | True | False | False | False | False |
3 | Grass | Poison | False | False | False | False | False | True | False | False | False | False | False | False | False | True | False | False | False | False |
4 | Fire | NaN | False | False | False | False | False | False | False | False | False | False | False | False | False | False | True | False | False | False |
Baseline
original_data.columns
Index(['#', 'Name', 'Type 1', 'Type 2', 'Total', 'HP', 'Attack', 'Defense',
'Sp. Atk', 'Sp. Def', 'Speed', 'Generation', 'Legendary'],
dtype='object')
여기서 안쓸 데이터인 # name type 12 legendary 를 빼주자
(우리가 할 건 나머지 데이터로 legendary 를 유추할 수 있는지이다.)
features = ['Total', 'HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed', 'Generation']
target = 'Legendary'
X = original_data[features]
print(X.shape)
X.head()
(800, 8)
Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | |
---|---|---|---|---|---|---|---|---|
0 | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 |
1 | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 |
2 | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 |
3 | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 |
4 | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 |
y = original_data[target]
print(y.shape)
y.head()
(800,)
0 False
1 False
2 False
3 False
4 False
Name: Legendary, dtype: bool
x 와 y는 각각 train_x train_y 이다
다시 말해 feature 와 target 이다
test 데이터를 꺼내주기 위헤 split 하자
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=15)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
(640, 8) (640,)
(160, 8) (160,)
모델 생성 및 학습
- Desicion tree (전처리로 추가한 데이터 -이름, 스텟 등- 넣기 전)
from sklearn.tree import DecisionTreeClassifier #모델
from sklearn.metrics import confusion_matrix #학습 지표
from sklearn.metrics import classification_report #학습 지표
model = DecisionTreeClassifier(random_state=25)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
confusion_matrix(y_test, y_pred)
array([[144, 3],
[ 5, 8]])
순서는 왼쪽위부터 TN FP FN TF
우리의 경우 True 가 전설의 포켓몬이다.
정확도를 생각해보면 152/160 해서 95%인데 이게 곧이곧대로 들으면 안될게
len(legendary)
65
800마리중 65마리만 레전드니까
애초에 다 일반 포켓몬이다 찍어도 735/800 = 91% 다 이거지
print(classification_report(y_test, y_pred))
precision recall f1-score support
False 0.97 0.98 0.97 147
True 0.73 0.62 0.67 13
accuracy 0.95 160
macro avg 0.85 0.80 0.82 160
weighted avg 0.95 0.95 0.95 160
바바 일반포켓몬 맞을 확률은 높은데 전설 포켓몬 고를 확률은 67%자너
우리는 전설의 포켓몬을 알고 싶은거니까 전설의 포켓몬인데 일반이라 하는 경우가 작아야돼 이건 FN 이 작아야 하고 이게 작으면 recall 값이 올라
그러니까 recall 값을바야 함 암튼 그럼
적은걸 잡아낼 수록 FN 을 봐야 합니다.
- Desicion tree (전처리로 추가한 데이터 -이름, 스텟 등- 넣은 후)
`우리가 아까 전처리했던 데이터를 다시 불러와보자
print(len(pokemon.columns))
print(pokemon.columns)
45
Index(['#', 'Name', 'Type 1', 'Type 2', 'Total', 'HP', 'Attack', 'Defense',
'Sp. Atk', 'Sp. Def', 'Speed', 'Generation', 'Legendary', 'name_count',
'long_name', 'Name_nospace', 'name_isalpha', 'Forme', 'Mega', 'Mewtwo',
'Kyurem', 'Deoxys', 'Hoopa', 'Latias', 'Latios', 'Kyogre', 'Groudon',
'Fighting', 'Psychic', 'Flying', 'Electric', 'Normal', 'Poison',
'Steel', 'Water', 'Fairy', 'Ice', 'Ghost', 'Ground', 'Dark', 'Grass',
'Fire', 'Bug', 'Dragon', 'Rock'],
dtype='object')
여기서 필요없는 것들을 빼주자.
#, name, type12, name_[a-Z]* , legend 이렇게 빼주면 될 듯
features = ['Total', 'HP', 'Attack', 'Defense',
'Sp. Atk', 'Sp. Def', 'Speed', 'Generation', 'name_count',
'long_name','Forme', 'Mega', 'Mewtwo','Kyurem', 'Deoxys',
'Hoopa', 'Latias', 'Latios', 'Kyogre', 'Groudon','Fighting',
'Psychic', 'Flying', 'Electric', 'Normal', 'Poison','Steel',
'Water', 'Fairy', 'Ice', 'Ghost', 'Ground', 'Dark', 'Grass',
'Fire', 'Bug', 'Dragon', 'Rock']
X = pokemon[features]
print(X.shape)
X.head()
(800, 38)
Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | name_count | long_name | ... | Fairy | Ice | Ghost | Ground | Dark | Grass | Fire | Bug | Dragon | Rock | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | 9 | False | ... | False | False | False | False | False | True | False | False | False | False |
1 | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | 7 | False | ... | False | False | False | False | False | True | False | False | False | False |
2 | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | 8 | False | ... | False | False | False | False | False | True | False | False | False | False |
3 | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | 21 | True | ... | False | False | False | False | False | True | False | False | False | False |
4 | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | 10 | True | ... | False | False | False | False | False | False | True | False | False | False |
5 rows × 38 columns
y = pokemon[target]
print(y.shape)
y.head()
(800,)
0 False
1 False
2 False
3 False
4 False
Name: Legendary, dtype: bool
이제 전처리 데이터를 다시 X 로 분류하였다 다시 학습을 돌려 보자.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=15)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
(640, 38) (640,)
(160, 38) (160,)
model = DecisionTreeClassifier(random_state=25)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
confusion_matrix(y_test, y_pred)
array([[141, 6],
[ 1, 12]])
print(classification_report(y_test, y_pred))
precision recall f1-score support
False 0.99 0.96 0.98 147
True 0.67 0.92 0.77 13
accuracy 0.96 160
macro avg 0.83 0.94 0.87 160
weighted avg 0.97 0.96 0.96 160
이렇게 새로운 데이터 전처리를 통해 전설의포켓몬 recall 값을 92% 까지 올렸다!