[Python] 데이터 사이언스 스쿨 - 4.1 판다스 패키지의 소개

Updated:

데이터 사이언스 스쿨 자료를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


4.1 판다스 패키지의 소개

시리즈 클래스

import numpy as np
import pandas as pd
# 시리즈 생성
pop = pd.Series([9904312, 3448737, 2890451, 2466052],
              index=["서울", "부산", "인천", "대구"])
pop
서울    9904312
부산    3448737
인천    2890451
대구    2466052
dtype: int64
  • 판다스 패키지의 시리즈 클래스는 dictionary와 비슷한 구조로 index와 values로 구성되어있다.
# 인덱스 확인
print(pop.index)

# 값 확인
print(pop.values)
Index(['서울', '부산', '인천', '대구'], dtype='object')
[9904312 3448737 2890451 2466052]
  • indexvalues 속성으로 시리즈의 index, values를 확인 할 수 있다.
# 정수 인덱스
pd.Series(range(10,14))
0    10
1    11
2    12
3    13
dtype: int64
  • 위 예시와 같이 index를 지정하지 않으면 정수 인덱스가 생성된다.
# 시리즈 이름 지정
pop.name = "인구"

# 인덱스 이름 지정
pop.index.name = "도시"

pop
도시
서울    9904312
부산    3448737
인천    2890451
대구    2466052
Name: 인구, dtype: int64
  • 위 예시와 같이 시리즈와 인덱스의 이름을 지정 가능하다.

  • 만약 시리즈를 데이터 프레임으로 만든다면 인덱스 이름은 행 인덱스 이름, 시리즈 이름은 열 인덱스 이름이 된다.

# 시리즈 연산
pop / 1000000
도시
서울    9.904312
부산    3.448737
인천    2.890451
대구    2.466052
Name: 인구, dtype: float64
  • 넘파이 배열과 마찬가지로 시리즈도 벡터화 연산이 가능하다.
# 시리즈 인덱스
print(pop[0], pop["서울"])
print("-"*30)

# 시리즈 fanxy indexing
print(pop[ [0,1,3] ])
print("-"*30)

print(pop[ ["서울","부산","대구"] ])
print("-"*30)

print(pop[(250e4 < pop) & (pop < 500e4)])  # 인구가 250만 초과, 500만 미만인 경우
print("-"*30)

# 시리즈 슬라이싱
print(pop[0:1])
9904312 9904312
------------------------------
도시
서울    9904312
부산    3448737
대구    2466052
Name: 인구, dtype: int64
------------------------------
도시
서울    9904312
부산    3448737
대구    2466052
Name: 인구, dtype: int64
------------------------------
도시
부산    3448737
인천    2890451
Name: 인구, dtype: int64
------------------------------
도시
서울    9904312
Name: 인구, dtype: int64
  • 시리즈도 넘파이 배열처럼 인덱싱, 배열 인덱싱, 슬라이싱이 가능하다.

  • 인덱싱, 배열 인덱싱, 슬라이싱은 시리즈의 행 인덱스를 기준으로 진행한다.

# 시리즈와 딕셔너리

# 딕셔너리 기능 사용 가능
print("서울" in pop) # in 연산자
print("-"*30)

for key, value in pop.items(): # items
    print(f"{key} = {value}")
print("-"*30)

# 딕셔너리로 시리즈 생성
pop2 = pd.Series({"서울": 9631482, "부산": 3393191, "인천": 2632035, "대전": 1490158},
               index=["부산", "서울", "인천", "대전"])
print(pop2)
True
------------------------------
서울 = 9904312
부산 = 3448737
인천 = 2890451
대구 = 2466052
------------------------------
부산    3393191
서울    9631482
인천    2632035
대전    1490158
dtype: int64
  • 앞서 잠깐 언급하였듯이 시리즈는 dictionary와 비슷한 구조로 dictionary의 함수를 사용 가능하다.

  • 여기선 in 연산자나 items()를 사용하였다.

  • 또한 dictionary로 시리즈를 생성할 수도 있는데 이 외에도 리스트, 넘파이 배열로도 시리즈 생성 가능하다.

# 인덱스 기반 연산
# 동일 인덱스에 대해서만 연산을 실행함
pop3 = pop - pop2
print(pop3)
print("-"*30)

# 값만 추출해서 연산
print(pop.values - pop2.values)
대구         NaN
대전         NaN
부산     55546.0
서울    272830.0
인천    258416.0
dtype: float64
------------------------------
[ 6511121 -6182745   258416   975894]
  • 시리즈의 연산은 인덱스를 기반으로 한다.

  • 위 예시에서 대구는 pop에만 존재하고 대전은 pop2에만 존재해서 연산이 안되고 NaN이 출력되었다.

  • NaN은 float형에서만 가능하므로 시리즈의 자료형이 int에서 float으로 변경된다.

# NaN 제거 출력
print(pop3.notnull())
print("-"*30)

print(pop3[pop3.notnull()])
대구    False
대전    False
부산     True
서울     True
인천     True
dtype: bool
------------------------------
부산     55546.0
서울    272830.0
인천    258416.0
dtype: float64
  • notnull() 함수를 사용하면 NaN 여부에 따라 Boolean 시리즈를 생성 가능하다.

  • 이를 활용해서 배열 인덱싱을 적용하면 NaN이 아닌 값만 추출 가능하다.

# 데이터의 갱신, 추가, 삭제
pop3 = pop3[pop3.notnull()]

pop3["부산"] = 1 # 갱신
pop3["대구"] = 2 # 추가
del pop3["인천"] # 삭제

pop3
부산         1.0
서울    272830.0
대구         2.0
dtype: float64
  • 시리즈는 데이터의 갱신, 추가, 삭제가 가능하고 dictionary와 방법은 비슷하다.

연습 문제 4.1.1

(1) 임의로 두 개의 시리즈 객체를 만든다.

(2) 모두 문자열 인덱스를 가져야 하며 두 시리즈에 공통적으로 포함되지 않는 라벨이 있어야 한다.

(3) 위에서 만든 두 시리즈 객체를 이용하여 사칙 연산을 한다.

# (1) (2) 시리즈 생성
s1 = pd.Series([100, 200, 300, 400, 500],
            index = ["A", "B", "C", "D", "E"])

s2 = pd.Series([4, 5, 6, 7, 8],
            index = ["A", "B", "C", "D", "F"])

# (3) 시리즈 사칙연산
s3 = s1 * s2
s3[s3.notnull()]
A     400.0
B    1000.0
C    1800.0
D    2800.0
dtype: float64

데이터 프레임 클래스

# 딕셔너리를 이용해서 데이터 프레임 생성
data = {
    "2015": [9904312, 3448737, 2890451, 2466052],
    "2010": [9631482, 3393191, 2632035, 2431774],
    "2005": [9762546, 3512547, 2517680, 2456016],
    "2000": [9853972, 3655437, 2466338, 2473990],
    "지역": ["수도권", "경상권", "수도권", "경상권"],
    "2010-2015 증가율": [0.0283, 0.0163, 0.0982, 0.0141]
}

# 컬럼은 딕셔너리의 키를 지정한다
columns = ["지역", "2015", "2010", "2005", "2000", "2010-2015 증가율"]

# 행 인덱스
index = ["서울", "부산", "인천", "대구"]

df = pd.DataFrame(data, index=index, columns=columns)
df
지역 2015 2010 2005 2000 2010-2015 증가율
서울 수도권 9904312 9631482 9762546 9853972 0.0283
부산 경상권 3448737 3393191 3512547 3655437 0.0163
인천 수도권 2890451 2632035 2517680 2466338 0.0982
대구 경상권 2466052 2431774 2456016 2473990 0.0141
  • 위 예시는 dictionary를 이용해서 데이터 프레임을 생성하였다.

  • dictionary로 데이터 프레임 생성시 열은 key의 갯수, 행은 key별 values의 갯수이다.

  • 시리즈와 비슷하게 index와 columns으로 구성되어 있는데 공통 index를 가진 시리즈의 결합이라 생각해도 된다.

  • 데이터 프레임 역시 리스트, 넘파이 배열 등으로도 생성 가능하다.

# 행 인덱스 확인
print(df.index)

# 열 인덱스 확인
print(df.columns)

# 값 확인
print(df.values)
Index(['서울', '부산', '인천', '대구'], dtype='object')
Index(['지역', '2015', '2010', '2005', '2000', '2010-2015 증가율'], dtype='object')
[['수도권' 9904312 9631482 9762546 9853972 0.0283]
 ['경상권' 3448737 3393191 3512547 3655437 0.0163]
 ['수도권' 2890451 2632035 2517680 2466338 0.0982]
 ['경상권' 2466052 2431774 2456016 2473990 0.0141]]
# 인덱스 이름 지정
df.index.name = "도시" # 행 인덱스 
df.columns.name = "특성" # 열 인덱스
df
특성 지역 2015 2010 2005 2000 2010-2015 증가율
도시
서울 수도권 9904312 9631482 9762546 9853972 0.0283
부산 경상권 3448737 3393191 3512547 3655437 0.0163
인천 수도권 2890451 2632035 2517680 2466338 0.0982
대구 경상권 2466052 2431774 2456016 2473990 0.0141
  • 시리즈처럼 index와 columns의 이름을 지정 가능하다.
# 데이터 전치
df.T
도시 서울 부산 인천 대구
특성
지역 수도권 경상권 수도권 경상권
2015 9904312 3448737 2890451 2466052
2010 9631482 3393191 2632035 2431774
2005 9762546 3512547 2517680 2456016
2000 9853972 3655437 2466338 2473990
2010-2015 증가율 0.0283 0.0163 0.0982 0.0141
  • 데이터 프레임은 전치 기능을 포함하여 넘파이 2차원 배열이 가지는 대부분의 속성이나 메소드를 지원 받는다.

연습 문제 4.1.2

다음 조건을 만족하는 임의의 데이터프레임을 하나 만든다.

(1) 열의 갯수와 행의 갯수가 각각 5개 이상이어야 한다.

(2) 열에는 정수, 문자열, 실수 자료형 데이터가 각각 1개 이상씩 포함되어 있어야 한다.

data = {
    "정수": [1,2,3,4,5],
    "문자열": ["학생", "분석가", "회계사", "영업사원", "텔러"],
    "실수1": [1.2, 3.4, 5.6, 7.8, 9.10],
    "실수2": [4.3, 2.1, 8.1, 4.2, 10],
    "실수3": [5.1, 4.3, 7.3, 3.5, 20]
}

index = ["ID_1", "ID_2", "ID_3", "ID_4", "ID_5"]
columns = ["정수", "문자열", "실수1", "실수2", "실수3"]

result = pd.DataFrame(data, index = index, columns = columns)
result
정수 문자열 실수1 실수2 실수3
ID_1 1 학생 1.2 4.3 5.1
ID_2 2 분석가 3.4 2.1 4.3
ID_3 3 회계사 5.6 8.1 7.3
ID_4 4 영업사원 7.8 4.2 3.5
ID_5 5 텔러 9.1 10.0 20.0
# 열 데이터의 갱신, 추가, 삭제
df["2010-2015 증가율"] = df["2010-2015 증가율"] * 100 # 갱신
df["2005-2010 증가율"] = ((df["2010"] - df["2005"]) / df["2005"] * 100).round(2) # 추가
del df["2005"] # 삭제

df
특성 지역 2015 2010 2000 2010-2015 증가율 2005-2010 증가율
도시
서울 수도권 9904312 9631482 9853972 2.83 -1.34
부산 경상권 3448737 3393191 3655437 1.63 -3.40
인천 수도권 2890451 2632035 2466338 9.82 4.54
대구 경상권 2466052 2431774 2473990 1.41 -0.99
  • 데이터 프레임은 열 단위로 데이터의 갱신, 추가, 삭제가 가능하다.
# 열 인덱싱
# 하나의 열 지정시 시리즈 형태
print(df["지역"])

# 하나의 열 지정하고 데이터 프레임 형태 
df[["지역"]]
도시
서울    수도권
부산    경상권
인천    수도권
대구    경상권
Name: 지역, dtype: object
특성 지역
도시
서울 수도권
부산 경상권
인천 수도권
대구 경상권
  • 기본적으로 데이터 프레임은 df["columns"] 형태로 열 인덱싱을 수행한다.

  • 하나의 열을 추출하면 시리즈로 변경되며 데이터 프레임을 유지하고 싶으면 대괄호를 한번 더 사용한다.

  • 가장 중요한 것은 대괄호를 통해 열 인덱싱을 수행한다는 것 즉, 열이 기준이라는 점이다.

# 여러 열 지정시 데이터 프레임 형태
df[["지역", "2005-2010 증가율"]]
특성 지역 2005-2010 증가율
도시
서울 수도권 -1.34
부산 경상권 -3.40
인천 수도권 4.54
대구 경상권 -0.99
  • 여러 열을 추출하면 데이터 프레임 형태로 부분 추출한다.
# 데이터 프레임 열 인덱스가 문자열인 경우 정수 인덱스 사용 불가
df[0]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

~\anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   2894             try:
-> 2895                 return self._engine.get_loc(casted_key)
   2896             except KeyError as err:


pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()


pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()


pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()


pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()


KeyError: 0


The above exception was the direct cause of the following exception:


KeyError                                  Traceback (most recent call last)

<ipython-input-21-4a36cdabfea5> in <module>
      1 # 데이터 프레임 열 인덱스가 문자열인 경우 정수 인덱스 사용 불가
----> 2 df[0]


~\anaconda3\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
   2900             if self.columns.nlevels > 1:
   2901                 return self._getitem_multilevel(key)
-> 2902             indexer = self.columns.get_loc(key)
   2903             if is_integer(indexer):
   2904                 indexer = [indexer]


~\anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   2895                 return self._engine.get_loc(casted_key)
   2896             except KeyError as err:
-> 2897                 raise KeyError(key) from err
   2898 
   2899         if tolerance is not None:


KeyError: 0
  • 데이터 프레임의 열 인덱스가 문자열인 경우 정수 인덱스를 사용하면 에러가 발생한다.
# 열 인덱스가 정수인 데이터 프레임 생성
df2 = pd.DataFrame(np.arange(12).reshape(3, 4))
df2
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
df2[[0,3]]
0 3
0 0 3
1 4 7
2 8 11
  • 데이터 프레임 열 인덱스가 처음부터 정수인 경우 정수 인덱스 사용 가능하다.
# 행 인덱싱
df[:1] # 첫 번째 행
특성 지역 2015 2010 2000 2010-2015 증가율 2005-2010 증가율
도시
서울 수도권 9904312 9631482 9853972 2.83 -1.34
df[1:2] # 두 번째 행
특성 지역 2015 2010 2000 2010-2015 증가율 2005-2010 증가율
도시
부산 경상권 3448737 3393191 3655437 1.63 -3.4
df["서울":"부산"] # 라벨 슬라이싱
특성 지역 2015 2010 2000 2010-2015 증가율 2005-2010 증가율
도시
서울 수도권 9904312 9631482 9853972 2.83 -1.34
부산 경상권 3448737 3393191 3655437 1.63 -3.40
  • 앞서 데이터 프레임은 기본적으로 열 인덱싱을 수행함을 확인하였다.

  • 만약 행 인덱싱을 하고 싶으면 슬라이싱을 이용해야 한다.

  • 나중에 loc()iloc()를 사용하면 인덱싱을 보다 쉽게 할 수 있다.

# 개별 데이터 인덱싱
df["2015"]["서울"]
9904312
  • 위 예시는 처음엔 데이터 프레임 열 인덱싱을 통해 2015년 열을 시리즈로 추출한다.

  • 다음으로 시리즈의 인덱싱을 통해 개별 데이터 값을 추출한다.

연습 문제 4.1.3

다음 데이터프레임에서 지정하는 데이터를 뽑아내거나 처리하라.

data = {
    "국어": [80, 90, 70, 30],
    "영어": [90, 70, 60, 40],
    "수학": [90, 60, 80, 70],
}
columns = ["국어", "영어", "수학"]
index = ["춘향", "몽룡", "향단", "방자"]
df = pd.DataFrame(data, index=index, columns=columns)

(1) 모든 학생의 수학 점수를 시리즈로 나타낸다.

(2) 모든 학생의 국어와 영어 점수를 데이터 프레임으로 나타낸다.

(3) 모든 학생의 각 과목 평균 점수를 새로운 열로 추가한다.

(4) 방자의 영어 점수를 80점으로 수정하고 평균 점수도 다시 계산한다.

(5) 춘향의 점수를 데이터프레임으로 나타낸다.

(6) 향단의 점수를 시리즈로 나타낸다.

# 데이터 생성
data = {
    "국어": [80, 90, 70, 30],
    "영어": [90, 70, 60, 40],
    "수학": [90, 60, 80, 70],
}
columns = ["국어", "영어", "수학"]
index = ["춘향", "몽룡", "향단", "방자"]
df = pd.DataFrame(data, index=index, columns=columns)
df
국어 영어 수학
춘향 80 90 90
몽룡 90 70 60
향단 70 60 80
방자 30 40 70
# (1) 수학 점수 시리즈
df["수학"]
춘향    90
몽룡    60
향단    80
방자    70
Name: 수학, dtype: int64
# (2) 모든 학생의 국어와 영어 점수 데이터 프레임
df[["국어","영어"]]
국어 영어
춘향 80 90
몽룡 90 70
향단 70 60
방자 30 40
# (3) 모든 학생의 각 과목 평균 점수를 새로운 열로 추가
df["평균"] = (( df["국어"] + df["영어"] + df["수학"]) / 3 ).round(2)
df
국어 영어 수학 평균
춘향 80 90 90 86.67
몽룡 90 70 60 73.33
향단 70 60 80 70.00
방자 30 40 70 46.67
# (4) 방자의 영어 점수를 80점으로 수정하고 평균 점수도 다시 계산
df.loc["방자", "영어"] = 80

df["평균"] = (( df["국어"] + df["영어"] + df["수학"]) / 3 ).round(2)
df
국어 영어 수학 평균
춘향 80 90 90 86.67
몽룡 90 70 60 73.33
향단 70 60 80 70.00
방자 30 80 70 60.00
# (5) 춘향의 점수를 데이터프레임 형태로
df[:1]
국어 영어 수학 평균
춘향 80 90 90 86.67
# (6) 향단의 점수를 시리즈 형태로
df.iloc[2]
국어    70.0
영어    60.0
수학    80.0
평균    70.0
Name: 향단, dtype: float64
  • 연습 문제를 풀면서 (6) 문제처럼 행을 시리즈로 만드는 것이 슬라이싱으로는 데이터 프레임으로만 만들어졌다.

  • 검색해보니 loc(), iloc() 라는 함수가 있었는데 이는 추후 챕터 4.3에서 설명이 있었다.

Leave a comment