Pandas Series
1. Series (시리즈) 객체
Series(시리즈)
는 Pandas의 1차원 데이터 배열로, 인덱스와 값
으로 이루어진 데이터 구조입니다. 인덱스는 값과 매핑된 고유한 이름이며, 값은 해당 인덱스의 데이터 값입니다. 여기서 인덱스는 우리가 일반적으로 사용했던 숫자형 인덱스가 외에도, 사용자가 지정한 데이터의 레이블을 사용할 수 있어 이름표 역할을 합니다.
Series는 NumPy의 ndarray를 기반으로 하여 모든 유형의 데이터를 보유하거나 저장할 수 있으며, 원하는 인덱스를 지정해서 인덱스로 설정할 수 있습니다. 이처럼 Series와 DataFrame은 리스트나 Numpy 배열과 유사하지만, 인덱스를 활용하여 데이터를 더 빠르게 검색하고 쉽고 효율적으로 조작 및 처리할 수 있습니다. 이러한 Series는 주로 DataFrame의 열을 나타내는 데 사용됩니다.
먼저 Series에 대해 간단히 살펴보도록 하겠습니다.
Series는 첫 번째 열은 인덱스, 두 번째 열은 값으로 이루어져 있습니다. 기본적으로 인덱스는 0부터 시작하고 정수값을 가지게 됩니다. 값은 리스트에 포함된 값들로 설정됩니다.
0 1
1 2
2 3
3 4
dtype: int64
0 1
1 2
2 3
3 4
dtype: int64
2. Series 생성
이번에는 Series를 여러 가지 방법을 통해 생성해보도록 하겠습니다.
먼저 라이브러리를 불러오도록 하겠습니다. Pandas 라이브러리는 pd
라는 이름으로 불러옵니다. 앞서 배운 NumPy도 함께 사용할 것이므로 NumPy도 함께 불러옵니다.
2.1 기본 인덱스를 사용한 Series 생성
2.1.1 List로 생성하기
첫 번째 방법은 앞에서 처음 만들어보았던 시리즈처럼 리스트를 사용하여 Series를 생성하는 것입니다. 이 방법은 기본 인덱스, 즉 0부터 시작하는 정수 인덱스를 가진 Series를 만들어냅니다.
리스트로 생성한 Series:
0 1
1 2
2 3
3 4
4 5
dtype: int64
리스트로 생성한 Series:
0 1
1 2
2 3
3 4
4 5
dtype: int64
2.1.2 NumPy 배열로 생성하기
NumPy 배열을 사용하여 Series를 생성할 수도 있습니다. 이 방법 역시 기본 인덱스를 사용하며, pd.Series(np.array([2, 4, 6, 8, 10]))
와 같이 작성하면 NumPy 배열 [2, 4, 6, 8, 10]
을 Series로 변환할 수 있습니다.
NumPy 배열로 생성한 Series:
0 2
1 4
2 6
3 8
4 10
dtype: int32
NumPy 배열로 생성한 Series:
0 2
1 4
2 6
3 8
4 10
dtype: int32
2.1.3 Scalar 값으로 생성하기
시리즈를 스칼라 값으로도 생성할 수 있습니다.
스칼라 값으로 시리즈를 생성할 경우, 따로 명시해주지 않아도 하나의 값이 인덱스의 수만큼 들어가게 됩니다.
스칼라 값으로 생성한 Series:
0 5
1 5
2 5
3 5
dtype: int64
스칼라 값으로 생성한 Series:
0 5
1 5
2 5
3 5
dtype: int64
2.1.4 RangeIndex
만약 인덱스를 지정하지 않은 Series를 생성하게 되면 Pandas에서는 자동으로 RangeIndex
를 생성해줍니다. 위의 예제에서 pd.Series([0, 1, 2, 3])
는 RangeIndex(start=0, stop=4, step=1)
인 인덱스가 생성됩니다. RangeIndex는 메모리를 절약하고 효율적으로 사용하기 위해 구현된 것이며 이로 인해 사이즈가 큰 데이터도 빠르게 처리할 수 있는 것입니다.
[ 0 10 20 30 40]
[ 0 10 20 30 40]
RangeIndex로 생성한 Series:
0 0
1 10
2 20
3 30
4 40
dtype: int32
RangeIndex로 생성한 Series:
0 0
1 10
2 20
3 30
4 40
dtype: int32
이렇게 생성된 인덱스를 묵시적 인덱스라고 합니다. **묵시적 인덱스(Implicit Index)**는 사용자가 직접 인덱스를 지정하지 않고, 판다스가 자동으로 생성하는 인덱스입니다. 묵시적 인덱스는 0부터 시작하는 정수형 인덱스로, 데이터프레임과 시리즈를 생성할 때 index 속성을 설정하지 않으면 자동으로 생성됩니다. 묵시적 인덱스는 순서대로 데이터를 조회할 수 있습니다.
2.2 인덱스 지정하여 Series 생성
2.2.1 리스트와 인덱스로 생성하기
리스트에 사용자가 직접 정의한 인덱스를 작성하여 Series를 생성할 수 있습니다. 예를 들어, pd.Series([1, 3, 5, 7, 9], index=['a', 'b', 'c', 'd', 'e'])
와 같이 작성하면 리스트를 인덱스 ['a', 'b', 'c', 'd', 'e']
와 함께 Series로 변환할 수 있습니다.
사용자 정의 인덱스를 가진 Series:
a 1
b 2
c 3
d 4
e 5
dtype: int64
사용자 정의 인덱스를 가진 Series:
a 1
b 2
c 3
d 4
e 5
dtype: int64
2.2.2 Dictionary로 생성하기
딕셔너리를 사용하여 Series를 생성할 수 있습니다. 이 경우 딕셔너리의 키(key)가 시리즈의 인덱스, 값(value)이 시리즈의 값으로 들어가게 됩니다. 예를 들어, pd.Series({'a': 1, 'b': 3, 'c': 5, 'd': 7, 'e': 9})
와 같이 작성하면 딕셔너리 {'a': 1, 'b': 3, 'c': 5, 'd': 7, 'e': 9}
를 Series로 변환할 수 있습니다.
딕셔너리로 생성한 Series:
a 1
b 3
c 5
d 7
e 9
dtype: int64
딕셔너리로 생성한 Series:
a 1
b 3
c 5
d 7
e 9
dtype: int64
이렇게 생성된 인덱스를 명시적 인덱스라고 합니다. **명시적 인덱스(Explicit Index)**는 사용자가 직접 지정한 인덱스입니다. 즉, 인덱스를 임의로 설정할 수 있으며, 이를 통해 데이터를 쉽게 조회할 수 있습니다. 명시적 인덱스는 인덱스 레이블을 사용하여 데이터를 조회할 수 있습니다.
이렇게 여러 방법을 통해 다양한 형태의 데이터를 Series로 생성할 수 있습니다. 기본 인덱스를 사용하거나 사용자가 직접 인덱스를 지정하여 상황에 맞는 적절한 방법을 선택하시면 됩니다.
3. Series 데이터 타입 및 형태 확인
데이터 타입과 형태 확인은 앞서 배운 NumPy와 유사합니다. s.dtype
은 Series에 저장된 데이터의 타입을 알려줍니다. s.shape
는 Series의 형태를 보여줍니다. s.size
는 Series에 포함된 요소의 총 개수를 반환합니다.
데이터 타입: int64
Series 형태: (5,)
요소 개수: 5
데이터 타입: int64
Series 형태: (5,)
요소 개수: 5
4. Series의 인덱싱, 슬라이싱
4.1 Indexing(인덱싱)
Series에서 인덱싱은 특정 위치의 데이터에 접근하는 방법입니다. NumPy 배열과 마찬가지로 위치 기반 인덱싱을 사용할 수 있으며, 레이블 인덱싱도 사용할 수 있습니다. 위치(숫자) 기반 인덱싱은 0부터 시작하는 정수를 사용하고, 레이블 인덱싱은 Series 생성 시 지정한 인덱스 이름을 사용합니다.
생성한 데이터를 인덱스를 기준으로 값을 가져와보도록 하겠습니다.
print(series4[0])
는 해당 시리즈의 0번 인덱스에 있는 값을 가져올 수 있습니다.
print(series6['a'])
는 해당 시리즈의 인덱스 a에 있는 값을 가져올 수 있습니다.
위치 기반 인덱싱 (첫번째 값):
1
레이블 인덱싱 (인덱스가 a인 값):
1
위치 기반 인덱싱 (첫번째 값):
1
레이블 인덱싱 (인덱스가 a인 값):
1
4.2 Slicing(슬라이싱)
Series에서 슬라이싱은 데이터의 특정 부분을 선택하여 추출하는 기능입니다. 슬라이싱은 NumPy 배열과 유사하게 동작하며, 인덱스를 사용하여 데이터를 선택합니다. 슬라이싱은 레이블 슬라이싱을 사용할 수도 있고, 위치(숫자) 기반 슬라이싱을 사용할 수도 있습니다. 여기서 주의할 점은, 레이블 슬라이싱을 사용할 때는 마지막 인덱스도 포함된다는 점입니다.
생성한 데이터를 인덱스를 기준으로 값을 가져와보도록 하겠습니다.
위치 기반 슬라이싱
슬라이싱 (0부터 3까지):
0 1
1 2
2 3
dtype: int64
슬라이싱 (1부터 4까지):
1 2
2 3
3 4
dtype: int64
슬라이싱 역순 출력:
4 5
3 4
2 3
1 2
0 1
dtype: int64
위치 기반 슬라이싱
슬라이싱 (0부터 3까지):
0 1
1 2
2 3
dtype: int64
슬라이싱 (1부터 4까지):
1 2
2 3
3 4
dtype: int64
슬라이싱 역순 출력:
4 5
3 4
2 3
1 2
0 1
dtype: int64
print(series4[0:3])
은 0번부터 2번(3에서 1을 뺀 값)까지의 인덱스 값을 가져올 수 있습니다.
print(series4[1:4])
은 1번부터 4번(4에서 1을 뺀 값)까지의 인덱스 값을 가져올 수 있습니다.
print(series4[::-1])
은 해당 시리즈의 값들을 역순으로 출력해줍니다.
레이블 슬라이싱
슬라이싱 (a부터 c까지):
a 1
b 3
c 5
dtype: int64
슬라이싱 (b부터 d까지):
b 3
c 5
d 7
dtype: int64
레이블 슬라이싱
슬라이싱 (a부터 c까지):
a 1
b 3
c 5
dtype: int64
슬라이싱 (b부터 d까지):
b 3
c 5
d 7
dtype: int64
print(series6['a':'c'])
은 a부터 c까지의 인덱스 값을 가져올 수 있습니다.
print(series6['b':'d'])
은 b부터 d까지의 인덱스 값을 가져올 수 있습니다.
4.3 Boolean Indexing(불리언 인덱싱)
불리언 인덱싱(Boolean Indexing)은 Series에서 특정 조건을 만족하는 데이터만 선택하는 강력한 기능입니다. 앞서 학습한 NumPy의 불리언 인덱싱과 유사하게 동작합니다.
20보다 큰 값:
c 30
d 40
dtype: int64
짝수인 값:
a 10
b 20
c 30
d 40
dtype: int64
선택된 값:
a 10
c 30
dtype: int64
20보다 큰 값:
c 30
d 40
dtype: int64
짝수인 값:
a 10
b 20
c 30
d 40
dtype: int64
선택된 값:
a 10
c 30
dtype: int64
series7[series7>20]]
는 시리즈 중 값이 20을 초과하는 값들을 가진 시리즈로 반환합니다.
series7[series7 % 2 == 0]
는 시리즈 값을 2로 나누었을 때 나머지 값이 0인 값을 시리즈로 반환합니다.
series7[[True, False, True, False]]
는 시리즈 중 값이 TRUE인 값을 시리즈로 반환합니다.
a False
b False
c True
d True
dtype: bool
a False
b False
c True
d True
dtype: bool
series7 > 20
은 20을 초과하는 지를 시리즈 전체에서 확인하여 각 값이 참인지 거짓인지 확인할 수 있는 방법입니다.
위의 코드처럼 결국 불리언 인덱싱은 True와 False로 이루어진 리스트를 사용하여 데이터를 선택합니다. 이를 통해 사용자는 원하는 데이터만 선택하여 처리할 수 있습니다. 여기서 주의해야 할 점은 불리언 인덱싱의 괄호가 두 개라는 것입니다. 이는 불리언 인덱싱이 먼저 수행되어야 하기 때문입니다.
3.5 팬시 인덱싱 (at, iat)
팬시 인덱싱(Fancy Indexing)은 Series에서 특정 데이터에 빠르고 효율적으로 접근하는 방법입니다. 이 방법에는 'at'과 'iat'이라는 두 가지 메서드가 있습니다. 먼저, 'at'은 레이블을 사용해 데이터에 접근합니다. 반면, 'iat'은 위치(숫자)를 사용해 데이터에 접근합니다.
기본 인덱싱 vs 펜시 인데싱
특성 | 기본 인덱싱 (s[...]) | at | iat |
---|---|---|---|
사용 방법 | s['c'] 또는 s[2] | s.at['c'] | s.iat[2] |
접근 기준 | 라벨 또는 정수 위치 | 라벨 | 정수 위치 |
다중 요소 선택 | 가능 | 불가능 | 불가능 |
슬라이싱 | 가능 | 불가능 | 불가능 |
불리언 인덱싱 | 가능 | 불가능 | 불가능 |
단일 요소 접근 속도 | 상대적으로 느림 | 빠름 | 가장 빠름 |
대규모 데이터 처리 | 다양한 작업에 적합 | 단일 요소 접근에 최적화 | 단일 요소 접근에 최적화 |
유연성 | 높음 | 제한적 | 제한적 |
존재하지 않는 인덱스 처리 | 상황에 따라 다름 | 즉시 에러 발생 | 즉시 에러 발생 |
3.5 팬시 인덱싱 (get)
get() 메서드는 배열에서 특정 인덱스의 요소를 추출하는 안전한 방법입니다. 이 메서드는 주어진 인덱스가 배열의 범위를 벗어나더라도 오류를 발생시키지 않고 대신 지정된 기본값을 반환합니다. get() 메서드는 주로 1차원 배열에서 사용되며, 배열명.get(인덱스, 기본값)
형식으로 호출됩니다. 여기서 '인덱스'는 가져오고자 하는 요소의 위치이고, '기본값'은 인덱스가 유효하지 않을 때 반환될 값입니다. 이 방법은 예외 처리 없이 안전하게 배열의 요소에 접근하고자 할 때 특히 유용합니다.
4. Series 정보 및 속성
4.1 데이터타입 및 형태 확인
데이터 타입과 형태 확인은 앞서 배운 NumPy와 유사합니다. s.dtype
은 Series에 저장된 데이터의 타입을 알려줍니다. s.shape
는 Series의 형태를 보여줍니다. s.size
는 Series에 포함된 요소의 총 개수를 반환합니다.
4.2 keys와 values
keys()
메서드는 Series의 인덱스를 반환합니다. 이는 Series의 인덱스 레이블을 확인하는 데 유용합니다. 예를 들어, for key in s.keys():
와 같이 사용하면 Series의 각 인덱스 레이블에 쉽게 접근할 수 있습니다.
values()
메서드는 Series의 값들을 반환합니다. 이는 Series의 데이터를 확인하는 데 유용합니다. 예를 들어, for value in s.values():
와 같이 사용하면 Series의 각 데이터에 쉽게 접근할 수 있습니다.
4.3 items
items()
메서드는 Series의 인덱스와 값을 튜플 형태로 반환합니다. 딕셔너리의 items()
메서드와 유사합니다. 이 메서드는 Series의 각 요소에 쉽게 접근할 수 있도록 도와줍니다. 예를 들어, for index, value in s.items():
와 같이 사용하면 Series의 각 요소에 대해 인덱스와 값을 쉽게 접근할 수 있습니다.
5. Series 연산 및 통계
5.1 기본 통계
기본 통계 메서드도 NumPy의 메서드와 비슷합니다. s.mean()
은 Series의 모든 값들의 평균을 계산합니다. s.median()
은 Series의 중앙값을 계산합니다. s.min()
과 s.max()
는 각각 Series의 최소값과 최대값을 반환합니다.
이러한 기본 통계를 한꺼번에 해주는 메서드도 있습니다. s.describe()
는 Series의 기본 통계 정보를 한 번에 보여줍니다. 이 메서드는 Series의 개수, 평균, 표준편차, 최소값, 중앙값, 최대값을 한 번에 보여줍니다.
5.2 Series 간 산술 연산
pandas에서는 Series 객체 간에 덧셈(+)
, 뺄셈(-)
, 곱셈(*)
, 나눗셈(/)
등의 기본적인 산술 연산을 직접 수행할 수 있습니다. 이러한 연산은 각 Series의 동일한 인덱스를 가진 요소들 사이에서 이루어집니다.
예를 들어, s1 + s2를 하면 s1의 'a' 인덱스 값과 s2의 'a' 인덱스 값이 더해지고, 'b' 인덱스 값끼리, 'c' 인덱스 값끼리 더해집니다. 다른 연산들도 마찬가지로 동작합니다.
산술연산을 상수로 더해보도록 하겠습니다.
이번에는 인덱스를 섞어보도록 하겠습니다.
실행 결과처럼 Series 간의 연산은 인덱스가 같은 요소끼리 수행됩니다. 인덱스가 다르면 NaN이 결과로 나타납니다.
6. 결측치 처리
6.1 결측치 확인, 제거, 채우기
s.isnull()
메서드는 Series의 각 요소가 결측치인지 여부를 확인합니다. 이 메서드는 각 요소에 대해 결측치면 True, 아니면 False를 반환하는 새로운 Series를 생성합니다. 이를 통해 데이터셋에서 결측치의 위치와 개수를 쉽게 파악할 수 있습니다. s.isnull().sum()
메서드는 결측치의 개수를 반환합니다. 실무에서도 자주 사용하는 메서드입니다.
s.dropna()
메서드는 결측치를 포함한 모든 행을 제거한 새로운 Series를 반환합니다. 이 방법은 결측치가 적고 데이터의 손실이 크지 않을 때 유용합니다. 하지만 너무 많은 데이터가 제거될 수 있으므로 신중하게 사용해야 합니다.
s.fillna()
메서드는 결측치를 지정된 값으로 대체합니다. 예를 들어, s.fillna(0)
은 모든 결측치를 0으로 채웁니다. 이 방법은 결측치를 특정 값(평균, 중앙값, 최빈값 등)으로 대체하고 싶을 때 유용합니다. 결측치 처리는 데이터 분석의 결과에 큰 영향을 미칠 수 있으므로 신중하게 접근해야 합니다. 어떤 방법을 선택할지는 데이터의 특성과 분석의 목적에 따라 달라집니다.
Tip !
데이터 분석을 할 때 결측치를 처리하는 것은 매우 중요합니다. isnull
로 결측치를 확인하고, dropna
로 결측치를 제거하거나 fillna
로 특정 값으로 대체할 수 있습니다. 다만 데이터가 클 경우 결측치를 하나의 값으로 대체하는 것은 데이터의 특성을 왜곡할 수 있으므로 주의해야 합니다. 아래와 같이 결측치를 여러 경우에 맞게 처리할 수도 있습니다. 아직 데이터 프레임을 배우진 않았지만, 단순히 결측치를 대체하는 것이 아니라 다양한 방법으로 처리할 수 있다는 것을 보여드리기 위하여 예시를 들었습니다.
6.2 NaN
NaN(Not a Number)은 데이터셋에서 결측치를 나타내는 대표적인 값이며, Python의 None 값도 Pandas에서는 결측치로 처리됩니다. NaN은 데이터 분석에서 중요한 개념으로, 주로 결측치나 정의되지 않은 값을 나타내는 데 사용됩니다.
NaN은 특히 수치형 데이터에서 자주 사용됩니다. 이는 데이터가 없거나, 계산할 수 없는 경우를 표현합니다. 예를 들어, 0으로 나누기를 시도하거나, 로그의 음수를 계산하려 할 때 NaN이 발생할 수 있습니다. pandas에서는 Python의 None 값도 NaN으로 취급합니다. 이는 데이터 처리의 일관성을 위한 것으로, 결측치를 다룰 때 유용합니다.
Tip !
NaN 값을 다룰 때는 주의가 필요합니다. NaN은 수학적 연산에서 특별하게 취급됩니다. 예를 들어, NaN과의 어떤 연산 결과도 NaN이 됩니다. 이는 데이터 분석 시 예상치 못한 결과를 낳을 수 있으므로, NaN 값의 존재와 그 영향을 항상 인지하고 있어야 합니다.
7. 데이터 변환 및 조작
7.1 Series 정렬
s.sort_values()
메서드는 Series의 값
을 기준으로 정렬합니다. 기본적으로 오름차순으로 정렬하지만, 'ascending=False' 매개변수를 사용하여 내림차순 정렬도 가능합니다. 이 메서드는 데이터의 크기나 빈도를 기준으로 정렬하고 싶을 때 유용합니다.
's.sort_index()'
메서드는 Series의 인덱스
를 기준으로 정렬합니다. 이 역시 기본적으로 오름차순이며, 'ascending=False'로 내림차순 정렬이 가능합니다. 이 메서드는 특히 시계열 데이터를 다룰 때 유용합니다.
7.2 함수 적용
s.apply()
메서드는 Series의 각 요소에 지정된 함수를 적용합니다. 이 함수는 단일 값을 인수로 받아 새로운 값을 반환해야 합니다. 특히 복잡한 연산이나 조건부 로직을 적용할 때 유용합니다. 예시 코드에서는 lambda 함수를 사용해 각 요소를 제곱하고 있습니다. 이 방법으로 데이터 정규화, 로그 변환, 조건부 값 변경 등 다양한 작업을 수행할 수 있습니다.
s.map()
메서드는 각 요소를 다른 값으로 매핑합니다. 이는 주로 딕셔너리나 함수를 사용하여 수행됩니다. 예시 코드에서는 딕셔너리를 사용해 인덱스 값을 대문자로 매핑하고 있습니다. 범주형 데이터를 숫자로 인코딩하거나, 코드를 실제 값으로 변환하는 등의 작업에 유용합니다.
생성된 Series에 대해 map()
함수를 사용하여 값에 대한 매핑을 수행합니다.
8. 고급 기능
8.1 MultiIndex
다중 인덱스를 생성하는 방법 중 하나는 MultiIndex.from_tuples
메서드를 사용하는 것입니다. 이 메서드는 튜플 리스트를 입력받아 각 튜플의 요소들을 인덱스의 각 레벨로 사용합니다. 또한, names
매개변수를 통해 각 레벨에 이름을 부여할 수 있어, 데이터의 의미를 더 명확하게 표현할 수 있습니다.
8.2 데이터 결합
pd.concat()
함수는 기본적으로 행 방향(위아래)으로 데이터를 결합합니다. 이는 'axis=0' 매개변수로 지정되며, 기본값입니다. 예시 코드의 첫 번째 결합에서 s1과 s2가 이러한 방식으로 결합되었습니다.
열 방향(좌우)으로 데이터를 결합하려면 'axis=1'을 지정합니다. 두 번째 예시에서 s1과 s3가 이 방식으로 결합되었습니다. 이 경우, 결과는 DataFrame이 되며, 두 Series의 인덱스가 새로운 DataFrame의 행 인덱스가 됩니다. 공통된 인덱스('a'와 'c')는 양쪽 데이터가 모두 표시되고, 한쪽에만 있는 인덱스('b'와 'e')는 다른 쪽에 NaN 값이 채워집니다.