[Python] 데이터 사이언스 스쿨 - 4.5 데이터프레임 인덱스 조작

Updated:

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

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


4.5 데이터프레임 인덱스 조작

데이터프레임 인덱스 설정 및 제거

import numpy as np
import pandas as pd
# 데이터 프레임 생성
np.random.seed(0)
df1 = pd.DataFrame(np.vstack([list('ABCDE'),
                              np.round(np.random.rand(3, 5), 2)]).T,
                   columns=["C1", "C2", "C3", "C4"])
df1
C1 C2 C3 C4
0 A 0.55 0.65 0.79
1 B 0.72 0.44 0.53
2 C 0.6 0.89 0.57
3 D 0.54 0.96 0.93
4 E 0.42 0.38 0.07
# set_index: 기존 열을 인덱스로 사용
df2 = df1.set_index("C1")
df2
C2 C3 C4
C1
A 0.55 0.65 0.79
B 0.72 0.44 0.53
C 0.6 0.89 0.57
D 0.54 0.96 0.93
E 0.42 0.38 0.07
  • set_index()를 이용하면 기존의 열을 행 인덱스로 사용 가능하다.
# 새로 인덱스 지정시 기존 인덱스는 사라짐
df2.set_index("C2")
C3 C4
C2
0.55 0.65 0.79
0.72 0.44 0.53
0.6 0.89 0.57
0.54 0.96 0.93
0.42 0.38 0.07
  • set_index()로 인덱스를 지정하면 기존 인덱스는 사라진다.
# reset_index: 인덱스를 보통의 자료열로 복구 - 인덱스는 정수 인덱스로 바뀜
df2.reset_index()
C1 C2 C3 C4
0 A 0.55 0.65 0.79
1 B 0.72 0.44 0.53
2 C 0.6 0.89 0.57
3 D 0.54 0.96 0.93
4 E 0.42 0.38 0.07
  • reset_index()를 사용하면 인덱스를 열로 사용한다.
# drop 옵션을 주면 자료가 복구되지 않고 버려짐
df2.reset_index(drop=True)
C2 C3 C4
0 0.55 0.65 0.79
1 0.72 0.44 0.53
2 0.6 0.89 0.57
3 0.54 0.96 0.93
4 0.42 0.38 0.07
  • drop 옵션을 True로 설정하면 인덱스를 열로 사용하지 않고 제거한다.

연습 문제 4.5.1

5명의 학생의 국어, 영어, 수학 점수를 나타내는 데이터프레임을 다음과 같이 만든다.

  1. 학생 이름을 나타내는 열을 포함시키지 않고 데이터프레임 df_score1 을 생성한 후, df_score1.index 속성에 학생 이름을 나타내는 열을 지정하여 인덱스를 지정한다. reset_index 명령으로 이 인덱스 열을 명령으로 일반 데이터열로 바꾸여 데이터프레임 df_score2을 만든다.

  2. 학생 이름을 나타내는 열이 일반 데이터 열을 포함하는 데이터프레임 df_score2set_index 명령을 적용하여 다시 학생 이름을 나타내는 열을 인덱스로 변경한다.

# 데이터 프레임 생성
np.random.seed(93)
df_score1 = pd.DataFrame(
    np.random.randint(0,101, size=(5,3)),
    columns = ["국어", "수학", "영어"]
)
df_score1
국어 수학 영어
0 37 91 4
1 24 79 97
2 85 48 77
3 52 52 51
4 21 49 74
# 인덱스 지정하기
df_score1.index = ["학생1", "학생2", "학생3", "학생4", "학생5"]
df_score1.index.name = "학생"
df_score1
국어 수학 영어
학생
학생1 37 91 4
학생2 24 79 97
학생3 85 48 77
학생4 52 52 51
학생5 21 49 74
# 1. reset_index
df_score2 = df_score1.reset_index()
df_score2
학생 국어 수학 영어
0 학생1 37 91 4
1 학생2 24 79 97
2 학생3 85 48 77
3 학생4 52 52 51
4 학생5 21 49 74
# 2. set_index
df_score2.set_index("학생")
국어 수학 영어
학생
학생1 37 91 4
학생2 24 79 97
학생3 85 48 77
학생4 52 52 51
학생5 21 49 74

다중 인덱스

# 다중 열 인덱스
np.random.seed(0)
df3 = pd.DataFrame(np.round(np.random.randn(5, 4), 2),
                   columns=[["A", "A", "B", "B"],
                            ["C1", "C2", "C1", "C2"]])
df3
A B
C1 C2 C1 C2
0 1.76 0.40 0.98 2.24
1 1.87 -0.98 0.95 -0.15
2 -0.10 0.41 0.14 1.45
3 0.76 0.12 0.44 0.33
4 1.49 -0.21 0.31 -0.85
  • 데이터 프레임을 생성할 때 위 예시와 같이 다중 열 인덱스를 생성 가능하다.

  • columns 옵션에 상위 인덱스부터 순서대로 작성한다.

# 열 인덱스 이름 지정
df3.columns.names = ["Cidx1", "Cidx2"]
df3
Cidx1 A B
Cidx2 C1 C2 C1 C2
0 1.76 0.40 0.98 2.24
1 1.87 -0.98 0.95 -0.15
2 -0.10 0.41 0.14 1.45
3 0.76 0.12 0.44 0.33
4 1.49 -0.21 0.31 -0.85
  • 다중 열 인덱스도 인덱스의 이름을 각각 지정 가능하다.

  • 마찬가지로 상위 인덱스 이름부터 순서대로 작성한다.

# 다중 행/열 인덱스
np.random.seed(0)
df4 = pd.DataFrame(np.round(np.random.randn(6, 4), 2),
                   columns=[["A", "A", "B", "B"],
                            ["C", "D", "C", "D"]],
                   index=[["M", "M", "M", "F", "F", "F"],
                          ["id_" + str(i + 1) for i in range(3)] * 2])
df4.columns.names = ["Cidx1", "Cidx2"]
df4.index.names = ["Ridx1", "Ridx2"]
df4
Cidx1 A B
Cidx2 C D C D
Ridx1 Ridx2
M id_1 1.76 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
F id_1 0.76 0.12 0.44 0.33
id_2 1.49 -0.21 0.31 -0.85
id_3 -2.55 0.65 0.86 -0.74
  • 행 인덱스도 같은 방법으로 다중 인덱스로 생성 가능하다.

행 인덱스와 열 인덱스 교환

# stack(열 인덱스 이름): 열 인덱스 -> 행 인덱스
df4.stack("Cidx1")
Cidx2 C D
Ridx1 Ridx2 Cidx1
M id_1 A 1.76 0.40
B 0.98 2.24
id_2 A 1.87 -0.98
B 0.95 -0.15
id_3 A -0.10 0.41
B 0.14 1.45
F id_1 A 0.76 0.12
B 0.44 0.33
id_2 A 1.49 -0.21
B 0.31 -0.85
id_3 A -2.55 0.65
B 0.86 -0.74
  • stack()을 사용해서 기존 열 인덱스의 이름을 지정하면 행 인덱스로 변경된다.

  • staack이라는 이름처럼 데이터가 세로로 쌓이게 된다.

df4.stack(1)
Cidx1 A B
Ridx1 Ridx2 Cidx2
M id_1 C 1.76 0.98
D 0.40 2.24
id_2 C 1.87 0.95
D -0.98 -0.15
id_3 C -0.10 0.14
D 0.41 1.45
F id_1 C 0.76 0.44
D 0.12 0.33
id_2 C 1.49 0.31
D -0.21 -0.85
id_3 C -2.55 0.86
D 0.65 -0.74
  • 위 예시와 같이 열 인덱스 이름이 아닌 열 인덱스 이름의 순서를 지정해도 같은 결과가 출력된다.
# unstack(행 인덱스 이름): 행 인덱스 -> 열 인덱스
df4.unstack("Ridx2")
Cidx1 A B
Cidx2 C D C D
Ridx2 id_1 id_2 id_3 id_1 id_2 id_3 id_1 id_2 id_3 id_1 id_2 id_3
Ridx1
F 0.76 1.49 -2.55 0.12 -0.21 0.65 0.44 0.31 0.86 0.33 -0.85 -0.74
M 1.76 1.87 -0.10 0.40 -0.98 0.41 0.98 0.95 0.14 2.24 -0.15 1.45
  • unstack()을 사용하여 기존 행 인덱스의 이름을 지정하면 열 인덱스로 변경된다.
df4.unstack(0)
Cidx1 A B
Cidx2 C D C D
Ridx1 F M F M F M F M
Ridx2
id_1 0.76 1.76 0.12 0.40 0.44 0.98 0.33 2.24
id_2 1.49 1.87 -0.21 -0.98 0.31 0.95 -0.85 -0.15
id_3 -2.55 -0.10 0.65 0.41 0.86 0.14 -0.74 1.45
  • stack()과 마찬가지로 정수를 지정해도 같은 결과가 출력된다.

다중 인덱스가 있는 경우의 인덱싱

df4
Cidx1 A B
Cidx2 C D C D
Ridx1 Ridx2
M id_1 1.76 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
F id_1 0.76 0.12 0.44 0.33
id_2 1.49 -0.21 0.31 -0.85
id_3 -2.55 0.65 0.86 -0.74
df4[ ("A", "C") ]
Ridx1  Ridx2
M      id_1     1.76
       id_2     1.87
       id_3    -0.10
F      id_1     0.76
       id_2     1.49
       id_3    -2.55
Name: (A, C), dtype: float64
  • 다중 인덱스가 존재한다면 인덱싱할 때 튜플 형식으로 지정해야한다.
df4.loc[ ("M","id_1"), :]
Cidx1  Cidx2
A      C        1.76
       D        0.40
B      C        0.98
       D        2.24
Name: (M, id_1), dtype: float64
  • loc 인덱서도 마찬가지로 튜플 형식으로 지정해야한다.
df4.loc[ ("M","id_1"), ("A", "C")] = 100
df4
Cidx1 A B
Cidx2 C D C D
Ridx1 Ridx2
M id_1 100.00 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
F id_1 0.76 0.12 0.44 0.33
id_2 1.49 -0.21 0.31 -0.85
id_3 -2.55 0.65 0.86 -0.74
  • 다중 인덱스에서 데이터 갱신도 튜플 형식으로 지정하면 문제없이 잘 수행된다.
df4.iloc[0:, 0:2]
Cidx1 A
Cidx2 C D
Ridx1 Ridx2
M id_1 100.00 0.40
id_2 1.87 -0.98
id_3 -0.10 0.41
F id_1 0.76 0.12
id_2 1.49 -0.21
id_3 -2.55 0.65
  • iloc 인덱서는 튜플 형태의 다중 인덱스 사용이 불가능하다.
df4["A"]
Cidx2 C D
Ridx1 Ridx2
M id_1 100.00 0.40
id_2 1.87 -0.98
id_3 -0.10 0.41
F id_1 0.76 0.12
id_2 1.49 -0.21
id_3 -2.55 0.65
  • 다중 인덱스가 존재하는데 그냥 인덱싱하면 가장 상위의 인덱스 레벨로 판단하고 출력한다.

  • 따라서 두 번째 레벨의 인덱스 값을 넣으면 애러가 발생한다.

df4.loc["M"]
Cidx1 A B
Cidx2 C D C D
Ridx2
id_1 100.00 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
  • loc 인덱서에서도 그냥 인덱싱하면 가장 상위의 인덱스 레벨로 판단하고 출력한다.

  • loc인덱서는 하나의 인덱싱은 행이 기준임을 한번 더 확인하자.

# 다중 인덱스에서 데이터 추가 - 행 추가
df4.loc[("All", "All"), :] = df4.sum()
df4
Cidx1 A B
Cidx2 C D C D
Ridx1 Ridx2
M id_1 100.00 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
F id_1 0.76 0.12 0.44 0.33
id_2 1.49 -0.21 0.31 -0.85
id_3 -2.55 0.65 0.86 -0.74
All All 101.47 0.39 3.68 2.28
  • 다중 인덱스에서 데이터 추가 역시 튜플 형식으로 지정해준다.
df4.loc[("M", slice(None)), ("A",slice(None))]
Cidx1 A
Cidx2 C D
Ridx1 Ridx2
M id_1 100.00 0.40
id_2 1.87 -0.98
id_3 -0.10 0.41
  • 만약 상위 레벨의 인덱스는 선택하고 해당 인덱스의 하위 레벨을 모두 출력하고 싶다면 slice(None)을 사용한다.

  • 위 예시는 행 인덱스는 M의 하위 레벨을 모두 포함하고, 열 인덱스는 A의 하위 레벨을 모두 포함해서 출력한다.

  • 일반적으로 콜론(:)을 사용한 반면 다중 인덱스에선 slice(None)을 사용하여야 한다는 것을 기억하자.

다중 인덱스의 인덱스 순서 교환

# 행 인덱스 레벨 교환
df5 = df4.swaplevel("Ridx1", "Ridx2")
df5
Cidx1 A B
Cidx2 C D C D
Ridx2 Ridx1
id_1 M 100.00 0.40 0.98 2.24
id_2 M 1.87 -0.98 0.95 -0.15
id_3 M -0.10 0.41 0.14 1.45
id_1 F 0.76 0.12 0.44 0.33
id_2 F 1.49 -0.21 0.31 -0.85
id_3 F -2.55 0.65 0.86 -0.74
All All 101.47 0.39 3.68 2.28
  • swaplevel(i, j)는 인덱스 i와 j를 교환하여 레벨이 변경된다.

  • 여기서 axis는 0이 디폴트이고 일반적으로 0이 열인데 반해 이 함수에선 행을 의미한다.

# 열 인덱스 레벨 교환
df6 = df4.swaplevel(0,1, axis=1)
df6
Cidx2 C D C D
Cidx1 A A B B
Ridx1 Ridx2
M id_1 100.00 0.40 0.98 2.24
id_2 1.87 -0.98 0.95 -0.15
id_3 -0.10 0.41 0.14 1.45
F id_1 0.76 0.12 0.44 0.33
id_2 1.49 -0.21 0.31 -0.85
id_3 -2.55 0.65 0.86 -0.74
All All 101.47 0.39 3.68 2.28

다중 인덱스가 있는 경우의 정렬

df5.sort_index(level=0)
Cidx1 A B
Cidx2 C D C D
Ridx2 Ridx1
All All 101.47 0.39 3.68 2.28
id_1 F 0.76 0.12 0.44 0.33
M 100.00 0.40 0.98 2.24
id_2 F 1.49 -0.21 0.31 -0.85
M 1.87 -0.98 0.95 -0.15
id_3 F -2.55 0.65 0.86 -0.74
M -0.10 0.41 0.14 1.45
  • 다중 인덱스가 존재한다면 정렬시에 level 옵션을 사용한다.

  • 여기서도 axis는 0이 디폴트이고 행을 의미한다.

# 열 인덱스 기준 정렬
df6.sort_index(axis=1, level=0)
Cidx2 C D
Cidx1 A B A B
Ridx1 Ridx2
M id_1 100.00 0.98 0.40 2.24
id_2 1.87 0.95 -0.98 -0.15
id_3 -0.10 0.14 0.41 1.45
F id_1 0.76 0.44 0.12 0.33
id_2 1.49 0.31 -0.21 -0.85
id_3 -2.55 0.86 0.65 -0.74
All All 101.47 3.68 0.39 2.28

연습 문제 4.5.2

A 반 학생 5명과 B반 학생 5명의 국어, 영어, 수학 점수를 나타내는 데이터프레임을 다음과 같이 만든다.

  1. “반”, “번호”, “국어”, “영어”, “수학” 을 열로 가지는 데이터프레임 df_score3을 만든다.

  2. df_score3을 변형하여 1차 행 인덱스로 “반”을 2차 행 인덱스로 “번호”을 가지는 데이터프레임 df_score4을 만든다.

  3. 데이터 프레임 df_score4에 각 학생의 평균을 나타내는 행을 오른쪽에 추가한다.

  4. df_score3을 변형하여 행 인덱스로 “번호”를, 1차 열 인덱스로 “국어”, “영어”, “수학”을, 2차 열 인덱스로 “반”을 가지는 데이터프레임 df_score5을 만든다.

  5. 데이터 프레임 df_score5에 각 반별 각 과목의 평균을 나타내는 행을 아래에 추가한다.

# 기초 배열 생성
np.random.seed(93)

data = []
for i in range(10):
    if i <=4:
        data.append( ["A",i+1] + list(np.random.randint(0,101,size=3)) )
    else:
        data.append( ["B",i+1] + list(np.random.randint(0,101,size=3)) )

data
[['A', 1, 37, 91, 4],
 ['A', 2, 24, 79, 97],
 ['A', 3, 85, 48, 77],
 ['A', 4, 52, 52, 51],
 ['A', 5, 21, 49, 74],
 ['B', 6, 33, 8, 71],
 ['B', 7, 80, 88, 9],
 ['B', 8, 55, 11, 5],
 ['B', 9, 44, 43, 46],
 ['B', 10, 15, 32, 91]]
# 1. df_score3 생성
df_score3 = pd.DataFrame(
    data, columns = ["반", "번호", "국어", "영어", "수학"]
)

df_score3
번호 국어 영어 수학
0 A 1 37 91 4
1 A 2 24 79 97
2 A 3 85 48 77
3 A 4 52 52 51
4 A 5 21 49 74
5 B 6 33 8 71
6 B 7 80 88 9
7 B 8 55 11 5
8 B 9 44 43 46
9 B 10 15 32 91
# 2. df_score3을 변형하여 1차 행 인덱스로 "반", 2차 행 인덱스로 "번호"을 가지는 데이터프레임 df_score4
df_score4 = df_score3.set_index(["반","번호"])
df_score4
국어 영어 수학
번호
A 1 37 91 4
2 24 79 97
3 85 48 77
4 52 52 51
5 21 49 74
B 6 33 8 71
7 80 88 9
8 55 11 5
9 44 43 46
10 15 32 91
# 3. df_score4에 각 학생의 평균을 나타내는 행을 오른쪽에 추가한다.
df_score4["mean_score"] = round(df_score4.mean(axis=1),2)
df_score4
국어 영어 수학 mean_score
번호
A 1 37 91 4 44.00
2 24 79 97 66.67
3 85 48 77 70.00
4 52 52 51 51.67
5 21 49 74 48.00
B 6 33 8 71 37.33
7 80 88 9 59.00
8 55 11 5 23.67
9 44 43 46 44.33
10 15 32 91 46.00
# 4. df_score3을 변형하여 행 인덱스로 "번호"를, 1차 열 인덱스로 "국어", "영어", "수학"을, 
# 2차 열 인덱스로 "반"을 가지는 데이터프레임 df_score5을 만든다.

temp = df_score3.set_index(["번호","반"])
df_score5 =temp.unstack("반").sort_index(level=0)
df_score5
국어 영어 수학
A B A B A B
번호
1 37.0 NaN 91.0 NaN 4.0 NaN
2 24.0 NaN 79.0 NaN 97.0 NaN
3 85.0 NaN 48.0 NaN 77.0 NaN
4 52.0 NaN 52.0 NaN 51.0 NaN
5 21.0 NaN 49.0 NaN 74.0 NaN
6 NaN 33.0 NaN 8.0 NaN 71.0
7 NaN 80.0 NaN 88.0 NaN 9.0
8 NaN 55.0 NaN 11.0 NaN 5.0
9 NaN 44.0 NaN 43.0 NaN 46.0
10 NaN 15.0 NaN 32.0 NaN 91.0
  • 번호가 1이고 B반인 학생은 없으므로 값이 NaN으로 뜬다.

  • 다른 NaN 값도 같은 경우이다.

  • 이 값을 0으로 대체하기엔 없는 학생의 점수를 0점으로 부여하는 것이라 그냥 두었다.

# 5. 데이터 프레임 df_score5에 각 반별 각 과목의 평균을 나타내는 행을 아래에 추가한다.
df_score5.loc["평균",:] = df_score5.mean()
df_score5
국어 영어 수학
A B A B A B
번호
1 37.0 NaN 91.0 NaN 4.0 NaN
2 24.0 NaN 79.0 NaN 97.0 NaN
3 85.0 NaN 48.0 NaN 77.0 NaN
4 52.0 NaN 52.0 NaN 51.0 NaN
5 21.0 NaN 49.0 NaN 74.0 NaN
6 NaN 33.0 NaN 8.0 NaN 71.0
7 NaN 80.0 NaN 88.0 NaN 9.0
8 NaN 55.0 NaN 11.0 NaN 5.0
9 NaN 44.0 NaN 43.0 NaN 46.0
10 NaN 15.0 NaN 32.0 NaN 91.0
평균 43.8 45.4 63.8 36.4 60.6 44.4

Leave a comment