지난 포스팅에 이어 이번 포스팅에서도 판다스 Series에 대해 알아보도록 하겠습니다.
import pandas as pd
import numpy as np
1. Series 인덱싱
Series를 인덱싱하는 방법은 2가지가 존재합니다.
- 일반적인 인덱싱 (파이썬과 넘파이식 인덱싱과 슬라이싱)
- 커스텀 인덱싱 (Series의 인덱스 레이블, 즉 index 속성을 사용)
- iloc, loc 접근자
2, 3번째 인덱싱 기법에 대해 집중적으로 알아보겠습니다.
✅ 커스텀 인덱싱
sales = [0, 5, 155, 0, 518]
items = ['coffee', 'banana', 'tea', 'coconut', 'sugar']
sales_series = pd.Series(sales, index=items, name='Sales')
sales_series
# coffee 0
# banana 5
# tea 155
# coconut 0
# sugar 518
# Name: Sales, dtype: int64
sales_series['banana':'coconut']
# banana 5
# tea 155
# coconut 0
# Name: Sales, dtype: int64
Series index 속성의 값들을 사용해서 인덱싱하는 방법입니다.
커스텀 인덱싱은 datetime 타입의 인덱스를 사용해서 Series를 인덱싱 할 때 유용합니다.
문자열을 인덱싱하는 경우에는 슬라이싱 범위의 끝점을 모두 포함합니다.
✅ iloc, loc 접근자
my_series = pd.Series(
[0, 1, 2, 3, 4], index=['day 0', 'day 1', 'day 2', 'day 3', 'day 4']
)
my_series
# day 0 0
# day 1 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.iloc[2]
# 2
my_series.iloc[[1, 4]]
# day 1 1
# day 4 4
# dtype: int64
# 오른쪽 끝점을 포함 X
my_series.iloc[1:3]
# day 1 1
# day 2 2
# dtype: int64
my_series.iloc[1:]
# day 1 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
iloc 접근자를 사용하면 위치(position-based)를 통해 Series 데이터에 접근할 수 있습니다.
별도의 커스텀 인덱스를 설정해도 iloc 접근자를 통해 Series의 데이터를 가져올 수 있습니다.
일반적인 슬라이싱 기법보다 효율적이며 판다스 개발자들이 권장하는 방법이기도 합니다.
다만 결측값 제거 등의 작업으로 인해 일부 데이터가 사라지는 경우, iloc 접근자를 사용해서 Series 데이터를 가져오게 되면 이전과는 다른 결과를 받아볼 수 있기 때문에 주의를 기울여야 합니다.
my_series = pd.Series(
[0, 1, 2, 3, 4], index=['day 0', 'day 1', 'day 2', 'day 3', 'day 4']
)
my_series
# day 0 0
# day 1 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.loc['day 2']
# 2
my_series.loc['day 1':'day 3']
# day 1 1
# day 2 2
# day 3 3
# dtype: int64
loc 접근자를 사용하면 커스텀 인덱스(label-based)를 통해 Series 데이터에 접근할 수 있습니다.
한 가지 기억할 점은 Series를 슬라이싱 하는 경우에 iloc과는 달리 끝점을 모두 포함한다는 사실입니다.
일반적으로 iloc 접근자보다 자주 사용합니다.
my_series = pd.Series(
[0, 1, 2, 3, 4],
index=['day 0', 'day 0', 'day 0', 'day 2', 'day 2']
)
my_series.index
# Index(['day 0', 'day 0', 'day 0', 'day 2', 'day 2'], dtype='object')
my_series.loc['day 0']
# day 0 0
# day 0 1
# day 0 2
# dtype: int64
my_series.loc['day 2']
# day 2 3
# day 2 4
# dtype: int64
my_series.reset_index(drop=True)
# 0 0
# 1 1
# 2 2
# 3 3
# 4 4
# dtype: int64
Series 또는 DataFrame 객체는 중복된 인덱스 레이블을 가질 수 있습니다.
이때 loc 접근자를 사용하면 중복된 인덱스에 해당하는 값들을 모두 가져오기 때문에 주의를 기울여야 합니다.
따라서 가급적이면 고유 인덱스를 갖도록 설정해야 합니다.
참고로 인덱스가 중복되는 경우에는 reset_index 메서드를 사용해서 기존 인덱스를 RangeIndex 객체로 초기화 할 수 있습니다. 그 결과 중복 인덱스는 사라지게 됩니다.
2. Series 필터링
Series를 필터링하는 2가지 방법에 대해 알아보겠습니다.
두 방법 모두 불리언 배열을 활용한다는 공통점이 있습니다.
✅ loc 접근자
my_series = pd.Series(
[0, 1, 2, 3, 4], index=['day 0', 'day 1', 'day 2', 'day 3', 'day 4']
)
my_series
# day 0 0
# day 1 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.loc[my_series > 2]
# day 3 3
# day 4 4
# dtype: int64
my_series.loc[my_series == 2]
# day 2 2
# dtype: int64
my_series.loc[~(my_series != 2)]
# day 2 2
# dtype: int64
loc 접근자와 비교연산자를 함께 사용하면 Series를 필터링 할 수 있습니다.
비교연산자를 사용해서 리턴 받은 불리언 배열을 loc 접근자에 인수로 전달하는 방법입니다.
✅ isin 메서드
my_series = pd.Series(
[0, 1, 2, 3, 4], index=['day 0', 'day 1', 'day 2', 'day 3', 'day 4']
)
my_series
# day 0 0
# day 1 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.loc[my_series.isin([1, 2])]
# day 1 1
# day 2 2
# dtype: int64
my_series.loc[~my_series.isin([1, 2])]
# day 0 0
# day 3 3
# day 4 4
# dtype: int64
isin 메서드는 Series가 특정값을 포함하는지 여부를 불리언 배열로 리턴합니다.
리턴 받은 불리언 배열을 사용해서 필터링을 수행합니다.
3. Series 정렬
Series의 값과 인덱스를 사용해서 Series를 정렬할 수 있습니다.
각 경우에 대해 알아보도록 하겠습니다.
✅ sort_values
my_series = pd.Series(
[1, 0, 2, 3, 4], index=['day 0', 'day 1', 'day 2', 'day 3', 'day 4']
)
my_series
# day 0 1
# day 1 0
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.sort_values()
# day 1 0
# day 0 1
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.sort_values(ascending=False)
# day 4 4
# day 3 3
# day 2 2
# day 0 1
# day 1 0
# dtype: int64
sort_values 메서드는 Series의 데이터를 기준으로 Series를 정렬합니다.
디폴트 정렬 방식은 오름차순입니다.
pd.Series(['jason', 'Harriet', 'Kelly', 'andy']).sort_values()
# 1 Harriet
# 2 Kelly
# 3 andy
# 0 jason
# dtype: object
알파벳 문자열 Series를 정렬하는 경우에는 소문자보다 대문자를 우선 정렬합니다.
battles.sort_values(na_position='last')
매개변수 na_position을 사용하면 Series 내 결측값들의 위치를 지정할 수 있습니다.
디폴트는 'last'로 결측값들을 Series 끝에 배치합니다.
반면에 'first' 인수를 전달하면 결측값들을 Series 처음에 배치합니다.
✅ sort_index
my_series
# day 4 4
# day 3 3
# day 2 2
# day 0 1
# day 1 0
# dtype: int64
my_series.sort_index()
# day 0 1
# day 1 0
# day 2 2
# day 3 3
# day 4 4
# dtype: int64
my_series.sort_index(ascending=False)
# day 4 4
# day 3 3
# day 2 2
# day 1 0
# day 0 1
# dtype: int64
sort_index 메서드는 Series의 인덱스를 기준으로 Series를 정렬합니다.
디폴트 정렬 방식은 오름차순입니다.
battles.sort_index()
시간/날짜 타입의 Series를 정렬하면 가장 오래된 날짜부터 표기됩니다.
이는 디폴트 정렬 방식이 오름차순이기 때문입니다.
매개변수 ascending에 False 인수를 전달하면 최신 날짜부터 표시합니다.
✅ nlargest, nsmallest
💡 새로운 Series를 리턴합니다.
- Series.nlargest(n)
- Series.nsmallest(n)
google.nlargest()
# Date
# 2019-04-29 1287.58
# 2019-04-26 1272.18
# 2018-07-26 1268.33
# 2019-10-25 1265.13
# 2019-04-23 1264.55
# Name: Close, dtype: float64
nlargest 메서드는 Series의 값 중 가장 큰 값 n개를 가져옵니다.
디폴트로 5개의 값을 가져오지만 리턴 받을 값의 개수를 지정할 수 있습니다.
google.nsmallest(3)
# Date
# 2004-09-03 49.82
# 2004-09-01 49.94
# 2004-08-19 49.98
# Name: Close, dtype: float64
nsmallest 메서드는 Series의 값 중 가장 작은 값 n개를 가져옵니다.
nlargest 메서드와 마찬가지로 문자열 Series에 대해서는 적용이 불가합니다.
4. Series 연산
Series 종류별 연산 방식에 대해 알아보도록 하겠습니다.
참고로 Series 수학 관련 메서드는 결측값을 무시합니다.
4-1. 숫자형 Series
기본적으로 사칙연산, 몫(//), 나머지(%), 지수(**) 관련 연산이 가능합니다.
비교적 간단하기 때문에 위 연산들에 대해서는 별도로 예제를 살펴보지 않겠습니다.
다음은 숫자 데이터 타입의 Series가 사용할 수 있는 메서드 목록입니다.
- count, prod, sum
- first, last
- mean, median, min, max, argmax, argmin, std, var, mad, quantile
transactions_series.iloc[:5]
# 0 770
# 1 2111
# 2 2358
# 3 3487
# 4 1922
# Name: transactions, dtype: int64
transactions_series.iloc[:5].quantile([.4], interpolation='nearest')
# 0.4 2111
# Name: transactions, dtype: int64
나머지 함수들은 익숙하기 때문에 quantile 메서드만 살펴보고 넘어가겠습니다.
quantile 메서드는 분위수와 관련이 있습니다.
아래 예제코드는 Series의 40%에 해당하는 데이터를 가져옵니다.
interpolation 매개변수에 nearest 인수를 전달하면 별도로 분위수를 계산하지 않고 Series 데이터에서 지정 분위수를 찾습니다.
s1 = pd.Series([1, 2, 3], index=['A', 'B', 'C'])
s2 = pd.Series([5, 6, 7], index=['A', 'B', 'C'])
print(s1 + s2)
# A 6
# B 8
# C 10
# dtype: int64
Series는 내부적으로 넘파이 배열처럼 동작하기 때문에 브로드캐스팅 연산 방식을 따릅니다.
4-2. 범주형 Series
series = pd.Series(['day 0', 'day 0', 'day 2', 'day 2', 'day 4'])
series
# 0 day 0
# 1 day 0
# 2 day 2
# 3 day 2
# 4 day 4
# dtype: object
✅ unique 메서드
series.unique()
# array(['day 0', 'day 2', 'day 4'], dtype=object)
type(series.unique())
# numpy.ndarray
unique 메서드는 Series 데이터에서 중복을 제거하고 고유한 값들을 리턴합니다.
참고로 unique 메서드가 리턴하는 결과의 타입은 넘파이 배열입니다.
✅ nunique 메서드
series.nunique()
# 3
nunique 메서드는 Series 데이터에서 중복을 제거한 고유값들의 개수를 리턴합니다.
✅ value_counts 메서드
series.value_counts()
# day 0 2
# day 2 2
# day 4 1
# dtype: int64
series.value_counts(normalize=True)
# day 0 0.4
# day 2 0.4
# day 4 0.2
# dtype: float64
value_counts 메서드는 Series 데이터별 개수를 새로운 Series에 담아 리턴합니다.
normalize 매개변수에 True 인수를 전달하면 Series 내 각 데이터의 비율을 계산합니다.
battles.value_counts(dropna=False).head()
# NaN 70
# South Carolina 31
# New York 28
# New Jersey 24
# Virginia 21
# Name: State, dtype: int64
매개변수 dropna에 False 인수를 전달하면 결측값을 카운팅 대상에 포함합니다.
참고로 value_counts는 기본적으로 결측값을 제외하고 값을 카운팅합니다.
5. Series 결측값
5-1. 결측값 찾기
my_series = pd.Series(range(5))
my_series.loc[1:2] = np.NaN
my_series
# 0 0.0
# 1 NaN
# 2 NaN
# 3 3.0
# 4 4.0
# dtype: float64
my_series.isna()
# 0 False
# 1 True
# 2 True
# 3 False
# 4 False
# dtype: bool
isna 메서드를 사용하면 Series의 각 값이 결측값인지 여부를 확인할 수 있습니다.
결측값이라면 True를, 결측값이 아니라면 False를 불리언 배열 형태로 리턴합니다.
isna 메서드의 결과로 리턴되는 불리언 배열은 마스킹 용도로 사용할 수 있습니다.
5-2. 결측값 다루기
Series의 결측값을 다루는 2가지 메서드에 대해 알아보겠습니다.
✅ dropna 메서드
my_series
# 0 0.0
# 1 NaN
# 2 NaN
# 3 3.0
# 4 4.0
# dtype: float64
my_series.dropna()
# 0 0.0
# 3 3.0
# 4 4.0
# dtype: float64
my_series.dropna().reset_index(drop=True)
# 0 0.0
# 1 3.0
# 2 4.0
# dtype: float64
기존 Series의 결측값을 제거한 새로운 Series를 리턴합니다.
✅ fillna 메서드
my_series
# 0 0.0
# 1 NaN
# 2 NaN
# 3 3.0
# 4 4.0
# dtype: float64
my_series.fillna(0)
# 0 0.0
# 1 0.0
# 2 0.0
# 3 3.0
# 4 4.0
# dtype: float64
my_series.fillna(my_series.mean())
# 0 0.000000
# 1 2.333333
# 2 2.333333
# 3 3.000000
# 4 4.000000
# dtype: float64
Series의 결측값을 다른 값으로 대체한 뒤 새로운 Series를 리턴합니다.
숫자형 보다는 범주형, 문자 데이터와 더 잘 어울립니다.
6. 파이썬과 Series
파이썬 내장함수의 인수로 Series를 전달할 수 있습니다.
✅ len
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
len(cities)
# 5
결측값을 포함한 Series의 데이터 개수를 리턴합니다.
✅ list
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
type(cities)
# <class 'pandas.core.series.Series'>
type(list(cities))
# <class 'list'>
Series를 리스트화합니다.
✅ dict
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
cities
# 0 New York
# 1 Tokyo
# 2 Seoul
# 3 Firenze
# 4 NaN
# dtype: object
dict(cities)
# {0: 'New York', 1: 'Tokyo', 2: 'Seoul', 3: 'Firenze', 4: nan}
Series를 딕셔너리화 합니다.
이때 Series의 인덱스는 딕셔너리의 key로, Series의 값은 딕셔너리의 value로 변환됩니다.
✅ in 키워드
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
cities
# 0 New York
# 1 Tokyo
# 2 Seoul
# 3 Firenze
# 4 NaN
'New York' in cities
# False
1 in cities
# True
특정 데이터가 Series의 인덱스에 존재하는지 확인할 수 있습니다.
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
'New York' in cities.values
# True
'Daegu' in cities.values
# False
Series의 values 속성과 함께 사용하면 특정값이 Series에 존재하는지 알 수 있습니다.
cities = pd.Series(['New York', 'Tokyo', 'Seoul', 'Firenze', np.nan])
'New York' not in cities.values
# False
'Daegu' not in cities.values
# True
마찬가지로 not in 키워드를 사용하면 미포함 여부를 확인할 수 있습니다.
마치며
이상으로 판다스의 기본 자료구조인 Series에 대한 정리를 마치도록 하겠습니다.
다음에 살펴볼 DataFrame 객체와 깊은 관련이 있는 만큼 복습을 철저히 해야겠습니다.
다음 포스팅부터는 본격적으로 데이터프레임에 대해 알아보도록 하겠습니다.
'파이썬・ML > pandas' 카테고리의 다른 글
판다스 데이터프레임 정리(2) 결측값/중복값/필터링/정렬 등 (0) | 2023.07.30 |
---|---|
판다스 데이터프레임 정리(1) 주요속성/메서드/조회 (0) | 2023.07.28 |
판다스 Series 정리(1) 특징/속성/생성법 (0) | 2023.07.27 |
판다스 데이터프레임과 피벗테이블 (0) | 2023.07.03 |
판다스 데이터프레임 MultiIndex (0) | 2023.07.02 |