Python 공부 기록-7

김시헌·2025년 4월 1일

인덱싱과 슬라이싱

넘파이는 파이썬 리스트를 확장해서 만들었기 때문에 리스트가 제공하는 대부분의 기능을 사용할 수 있다. 하나의 데이터를 선택하는 인덱싱과 하나 이상의 데이터를 가져오는 슬라이싱

코드

import numpy as np 

arr = np.arange(4)
print(arr[0])

실행 결과
0

2차원 ndarray의 경우 실행해보기

코드

import numpy as np 

arr = np.arange(4)
print(arr[0])

arr = np.arange(4).reshape(2, 2)
print(arr[0])

실행 결과
0
[0 1]

이차원 데이터의 [행, 열] 인덱싱의 실행 시간 측정해보기

코드

a=np.arange(10000).reshape(100, 100)
%timeit a[0, 50]

실행 결과
80.2 ns ± 16.1 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

이번에는 두 번에 걸쳐 연쇄적으로 인덱싱하는 시간 측정해보기

코드

a=np.arange(10000).reshape(100, 100)
%timeit a[0][50]

실행 결과
162 ns ± 16.1 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

0번 위치부터 1번 위치까지의 데이터를 슬라이싱해보기

코드

arr=np.arange(4)
print(arr[ :2])

실행 결과
[0 1]

리스트의 기능에 추가해서 ndarray는 불연속적인 데이터를 슬라이싱할 수 있다.

코드

target = [0,2]
print(arr[target])

라인 1: 가져오려는 데이터의 인덱스를 리스트로 정의한다. 이때 인덱스는 데이터 하나하나에 맵핑되는 숫자이다.

라인 2: 가져오려는 데이터의 위치 정보가 담긴 리스트를 사용해서 ndarray를 슬라이싱 한다.

실행 결과
[0 2]

이차원 데이터의 슬라이싱도 사용해보기

코드

arr=np.arange(20).reshape(4, 5)
print(arr[ :2])

실행 결과

이번에는 왼쪽 끝의 두 개 컬럼을 슬라이싱해보기

코드

result = [ ]
for row in arr:
    row_01 = [ row[0], row[1] ]
    result.append(row_01)

print(arr[ : , :2 ])

실행 결과

4행 5열의 ndarray에서 그림 2.4.1과 같이 일부 영역을 슬라이싱해보기

코드

print(arr[ 1:4, 2:5])
print(arr[ 1: , 2: ])

실행 결과

조건 연산

ndarray와의 연산은 브로드캐스팅이 적용되어 전체 데이터에 연산이 반복 적용됩니다. 다음은 ndarray와 스칼라의 비교 연산 결과를 출력한다.

코드

arr = np.array([10, 20, 30])
print(arr > 10)

실행 결과
[False True True]

2.4절에서 인덱스의 리스트로 슬라이싱한 것과 유사하게 True와 False가 담긴 ndarray로 조건이 참(True)인 데이터만을 슬라이싱할 수 있다.

코드

arr = np.array([10, 20, 30])
cond = [False, True, True]
print(arr[ cond ])

실행 결과
[20 30]

비교 연산이 간단하기 때문에 코드를 한 줄로 표현할 수도 있습니다.

코드

print(arr[ arr > 10 ])

실행 결과
[20 30]

브로드 캐스팅

연산이 ndarray로 확장 적용되는 것을 브로드캐스팅(broadcasting)이라고 한다. 브로드캐스팅은 '방송'이라는 뜻도 있지만 '전파하다'라는 의미도 있다

같은 크기를 갖는 두 개의 ndarray간의 연산

코드

a = np.array( [ 1, 2, 3] )
b = np.array( [ 2, 3, 4] )
print( a + b )
print( a * b )
print( a % b )

실행 결과

함수와 메서드

ndarray의 데이터를 합하는 sum메서드를 사용해보기

코드

arr = np.arange(8).reshape(4, 2)
print(arr.sum())

실행 결과
28
4행 2열의 ndarray에 sum 메서드를 적용하니 전체 데이터의 합이 계산된다.

sum 메서드에 축정보를 지정할 수 있습니다. 파이썬 기본 함수 sum에서는 사용할 수 없는 기능이다.

코드

print(arr.sum(axis=0))
print(arr.sum(axis=1))

실행 결과

axis=0은 x축 방향으로 데이터의 합을 구하라는 의미로 각각의 행 단위로 합을 구하고 그 결과를 ndarray로 반환한다.
axis=1은 y축 방향으로 합을 구해서 열 단위로 데이터의 합을 계산한다.
axis 파라미터를 입력하지 않으면 default로 axis=None이 되며, 이는 전체 데이터의 합을 계산한다.

numpy를 사용해서 임의의 숫자를 만들 수도 있다.

코드

np.random.randint(3)

실행 결과
0

size 파라미터를 사용하면 생성되는 숫자의 개수를 지정할 수 있다.

코드

np.random.randint(46, size=5)
np.random.randint(46, size=(2, 5))

size 파라미터에 하나의 값 혹은 튜플로 크기를 지정할 수 있다. 튜플을 입력했다면 앞의 숫자가 로우의 길이, 뒤의 숫자가 컬럼의 길이이다.

실행 결과
array([[36, 11, 23, 8, 17],
[15, 18, 44, 38, 34]])

라인1: 0부터 45까지의 수를 5개 생성해서 ndarray로 반환한다.

라인2: 0부터 45까지의 수를 2행 5열의 이차원 ndarray로 반환한다.

판다스

판다스는 데이터 수집 및 데이터 전처리를 위한 다양한 편의 기능을 지원하며, 그 편의성으로 인해 데이터 분석 분야에서 널리 사용된다. 넘파이를 확장해서 만든 판다스는 일차원 데이터를 위한 시리즈와 이차원 데이터를 위한 데이터프레임의 핵심 자료 구조로 구성되어 있다.

파이썬 자료구조 비교

다양한 Series 임포트 방식

시리즈 생성

리스트와 튜플의 장점을 섞어 놓은 것과 같이 동작하는 시리즈에 대해 본격적으로 배워보기

코드

from pandas import Series
data = [10, 20, 30]
s = Series(data)
print(s)

실행 결과
0 10
1 20
2 30
dtype: int64

라인 1: Series를 사용하기 위해 모듈을 import한다.

라인 3: 데이터를 파이썬 리스트로 저장한다.

라인 4: 시리즈 객체를 생성한다. 이때 data라는 변수가 바인딩하는 파이썬 리스트가 초기화자의 인자로 전달된다. 생성된 시리즈 객체를 s라는 변수가 바인딩한다.

넘파이를 사용해서 시리즈를 생성할 수도 있다. 시리즈 객체를 생성할 때 ndarray객체를 넘겨주면 된다.

코드

from pandas import Series
import numpy as np

data = np.arange(5)
s = Series(data)
print(s)

실행 결과
0 0
1 1
2 2
3 3
4 4
dtype: int64

시리즈에는 수치형 데이터뿐만 아니라 문자열 데이터도 저장할 수 있다. 다음은 두 개의 문자열을 저장하는 시리즈 객체를 생성한다.

코드

data = ["시가", "고가"]
s = Series(data)
print(s)

실행 결과
0 시가
1 고가
dtype: object

인덱스에는 자동으로 생성된 RangeIndex가 들어 있으며, 데이터 타입은 오브젝트(object)입니다. 판다스에서 문자열은 object로 표기되니 참고할것.

만약 다음과 같이 파이썬 문자열과 정수형 타입을 하나의 시리즈로 만들면, 전체 데이터가 object 타입으로 관리된다. object 타입의 시리즈는 브로드캐스팅 기능을 활용한 수치 연산을 사용할 수 없어서 시리즈의 효용성이 떨어지기 때문에 시리즈에는 같은 데이터 타입을 저장하는 것이 좋다.

코드

s = Series(['samsung', 81000])
print(s)

실행 결과
s = Series(['samsung', 81000])
print(s)

시리즈 인덱스

파이썬의 딕셔너리를 사용하면 데이터에 레이블을 붙여서 저장할 수 있는 것처럼 시리즈도 각 데이터에 인덱스를 설정할 수 있다. 인덱스의 설정을 하지 않으면, 0부터 시작하는 숫자 값을 RangeIndex 타입으로 생성한다. 시리즈를 만들고 인덱스를 얻어와 보자

코드

from pandas import Series

data = [1000, 2000, 3000]
s = Series(data)
print(s.index)
print(s.index.to_list())

실행 결과
RangeIndex(start=0, stop=3, step=1)
[0, 1, 2]

시리즈 객체를 생성한 후에 인덱스를 수정할 수 있다. index라는 변수에 리스트 혹은 ndarray로 데이터를 전달하면 자동으로 맵핑된 RangeIndex가 제거되고 지정한 값으로 대체된다. 당연하게도 데이터와 같은 개수의 인덱스를 넣어야 한다.

코드

data = [1000, 2000, 3000]
s = Series(data)
s.index = ["메로나", "구구콘", "하겐다즈"]
print(s)

실행 결과
메로나 1000
구구콘 2000
하겐다즈 3000
dtype: int64

출력 결과를 살펴보면 파이썬 딕셔너리처럼 각 데이터마다 레이블이 달려있는 것을 확인할 수 있다. 리스트에 저장된 순서대로 index와 data가 맵핑된다.

이번에는 시리즈를 생성할 때 인덱스를 같이 지정해 보자. 시리즈 객체를 생성할 때 두 번째 인자로 리스트로 정의된 인덱스 값을 전달한다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]

s = Series(data, index)
print(s)

실행 결과
메로나 1000
구구콘 2000
하겐다즈 3000
dtype: int64

라인 1: 시리즈에 저장될 데이터를 리스트로 정의한다.

라인 2: 시리즈에 저장될 인덱스를 리스트로 정의한다.

라인 4: 시리즈를 생성할 때 데이터와 인덱스를 차례로 전달한다.

reindex 메서드를 사용하면 인자로 전달한 새로운 값으로 맞춰 인덱스를 변경한다. 이때 기존에 있던 인덱스는 기존 값을 그대로 사용하고, 새로운 인덱스에는 NaN 값을 채운다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
s2 = s.reindex(["메로나", "비비빅", "구구콘"])
print(s2)

실행 결과
메로나 1000.0
비비빅 NaN
구구콘 2000.0
dtype: float64

변수 s에 값이 존재하는 메로나와 구구콘은 그대로 값을 가져왔으며, 비비빅은 존재하지 않아 결측값(NaN)으로 처리됐습니다.

실전 데이터를 사용해서 시리즈를 연습해 보자. 표의 삼성전자의 5일 종가를 시리즈 객체로 표현해 보자. 인덱스는 문자열로 표현된 날짜를 사용한다.

코드

price = [42500, 42550, 41800, 42550, 42650]
data = ["2019-05-31", "2019-05-30", "2019-05-29", "2019-05-28", "2019-05-27"]
s = Series(price, data)
print(s)

실행 결과
2019-05-31 42500
2019-05-30 42550
2019-05-29 41800
2019-05-28 42550
2019-05-27 42650
dtype: int64

라인 1: 종가를 파이썬 리스트로 저장한다.

라인 2: 각 거래일의 날짜를 문자열로 표현한 후 이를 리스트에 저장한다.

라인 3: 시리즈 객체를 생성합니다. 이때 데이터와 인덱스를 지정한다.

변수 s가 바인딩하는 값을 확인해보면 문자열 타입으로 표현된 날짜가 시리즈 객체의 인덱스로 사용된 것을 확인할 수 있다.

딕셔너리를 사용해서 시리즈를 한 번에 만들 수도 있다. 딕셔너리의 key가 시리즈의 인덱스로 value가 시리즈의 데이터로 구성된다. 출력되는 결과는 데이터와 인덱스를 리스트로 정의한 코드와 같다.

코드

data = {
    "2019-05-31" : 42500,
    "2019-05-30" : 42550,
    "2019-05-29" : 41800,
    "2019-05-28" : 42550,
    "2019-05-27" : 42650
}
s = Series(data)

print(s.index)
print(s.index.dtype)

실행 결과
Index(['2019-05-31', '2019-05-30', '2019-05-29', '2019-05-28', '2019-05-27'], dtype='object')
object

Index 객체는 리스트와 유사한 자료구조로 숫자를 사용한 인덱싱과 슬라이싱을 사용할 수 있다. Index 객체의 dtype에는 자료구조에 저장된 데이터 타입이 들어 있다.

values 값도 확인해 봅시다.
코드

print(s.values)

실행 결과
[42500 42550 41800 42550 42650]

출력되는 결과를 참고하면 리스트와 유사하지만, 데이터 사이에 콤마가 없다. 2장에서 배운 ndarray이다. 시리즈에서 데이터를 가져오고 싶을 때 values 변수를 사용하면 된다.

시리즈 인덱싱

자료구조에서 하나의 값에 접근하는 것을 인덱싱이라고 한다. 리스트는 인덱스로만 인덱싱을 했다면 시리즈는 행 번호와 인덱스를 사용해서 인덱싱을 할 수 있다.

우선 iloc 연산을 사용해서 행 번호로 인덱싱해 보자. 행 번호를 사용하는 인덱싱은 리스트의 정수 인덱싱과 동일하다. 양수 값과 음수 값을 모두 사용할 수 있다.

코드

from pandas import Series

data = [1000, 2000, 3000]
s = Series(data=data)

print(s.iloc[0])
print(s.iloc[1])
print(s.iloc[2])
print(s.iloc[-1])

실행 결과
1000
2000
3000
3000

라인 6: 행 번호 0의 데이터를 출력한다.

라인 7: 행 번호 1의 데이터를 출력한다.

라인 8: 행 번호 2의 데이터를 출력한다.

라인 9: 행 번호 -1의 위치의 데이터인 3000을 출력한다.

이번에는 loc 연산을 사용해보자. loc 연산은 시리즈의 인덱스 값을 통해 인덱싱한다. 따라서 표의 시리즈는 loc 로 인덱스 0, 1, 2를 사용해서 데이터에 접근할 수 있다. 하지만 s.loc[-1]은 -1이라는 인덱스가 시리즈 객체에 없으므로 에러가 발생한다.

코드

print(s.loc[0])
print(s.loc[1])
print(s.loc[2])
print(s.loc[-1]) #에러

실행 결과
1000
2000
3000
에러문

이번에는 문자열 인덱스가 설정된 시리즈 객체에 대한 인덱싱을 살펴보자. 문자열 인덱스가 설정된 경우에도 loc 연산은 인덱스를 iloc는 행 번호를 사용한다는 것만 기억하면 된다. 라인 5와 라인 6 두 코드는 모두 메로나의 가격을 출력한다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)

print(s.iloc[0])
print(s.loc['메로나'])

실행 결과
1000
1000

시리즈 슬라이싱

파이썬 자료구조에서 연속적인 범위의 값을 가져오고자 할 때 슬라이싱을 사용한다.

iloc 연산을 사용해서 슬라이싱해 보자. iloc[시작 행 번호:끝 행 번호]와 같이 시작과 끝으로 사용할 두 개의 행 번호를 입력한다.

코드

from pandas import Series

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
print(s.iloc[0:2])

실행 결과
메로나 1000
구구콘 2000
dtype: int64

이번에는 loc 연산을 사용해서 메로나에서 구구콘까지를 슬라이싱 해보자. loc 연산이므로 인덱스를 사용해서 loc[시작 인덱스:끝 인덱스]로 표현한다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)

print(s.loc['메로나':'구구콘'])

실행 결과
메로나 1000
구구콘 2000
dtype: int64

시리즈 객체는 파이썬 리스트와 달리 연속적이지 않은 값들에 대해서도 슬라이싱 할 수 있다. 접근하고자 하는 데이터의 행 번호나 인덱스를 리스트로 표현한 후 iloc나 loc 연산에서 리스트를 사용한 슬라이싱을 적용하면 된다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)

indice = [0, 2]
print(s.iloc[ indice ])
print(s.iloc[ [0, 2] ])

실행 결과
메로나 1000
하겐다즈 3000
dtype: int64
메로나 1000
하겐다즈 3000
dtype: int64

라인 5: 행 번호를 의미하는 0번과 2번을 리스트로 저장한다.

라인 6: 리스트에는 인덱스가 저장돼 있으므로 iloc로 슬라이싱한다.

라인 7: 라인5, 라인6의 코드는 간단하기 때문에 한 줄로 표현할 수 있다. 대괄호가 중첩해서 사용돼 이상해 보일 수 있지만, 이는 판다스 시리즈의 올바른 문법이다. 바깥쪽의 대괄호는 인덱싱 기호로, 안쪽의 대괄호는 리스트 기호이다.

iloc 뿐만 아니라 loc를 사용해서 불연속적인 데이터를 슬라이싱 할 수 있다. 리스트에 문자열 인덱스를 저장하고, loc 연산으로 슬라이싱한다.

코드

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)

indice = ["메로나", "하겐다즈"]
print(s.loc[ indice ])
print(s.loc[ ["메로나", "하겐다즈"] ])

실행 결과
메로나 1000
하겐다즈 3000
dtype: int64
메로나 1000
하겐다즈 3000
dtype: int64

0개의 댓글