-
고수준의 자료구조와 파이썬을 통한 빠르고 쉬운 데이터 분석 도구 제공, 넘파이의 배열 기반의 계산 스타일을 많이 차용함
판다스, 넘파이의 가장 큰 차이점은 판다스는 표 형식의 데이터나 다양한 형태의 데이터를 다루는 데 초점을 맞춰 설계, 넘파이는 단일 산술 배열 데이터를 다루는 데 특화되어 있음
넘파이와 판다스의 import 컨벤션
In [1]: import numpy as np In [2]: import pandas as pd
Series와 DataFrame은 로컬 네임스페이스로 임포트하는 것이 편하므로 다음과 같이 사용
In [3]: from pandas import Series, DataFrame
판다스 자료구조 소개
두 가지 자료구조 Series와 DataFrame
Series
일련의 객체를 담을 수 있는 1차원 배열 같은 자료구조(어떤 넘파이 자료형이라도 담을 수 있음)
그리고 색인(index)라 하는 배열의 데이터와 연관된 이름을 가짐, 가장 간단한 Series 객체는 배열 데이터로부터 생성
In [4]: obj = pd.Series([4, 7, -5, 3]) In [5]: obj Out[5]: 0 4 1 7 2 -5 3 3 dtype: int64
왼쪽에는 인덱스, 오른쪽에는 인덱스 값을 보여 줌
데이터의 인덱스를 지정하지 않으면 기본 인덱스인 0부터 N-1까지의 숫자 표시
Series의 배열과 색인 객체는 각각 array와 index 속성을 통해 얻을 수 있음
In [6]: obj.array Out[6]: <PandasArray> [4, 7, -5, 3] Length: 4, dtype: int64 In [7]: obj.index Out[7]: RangeIndex(start=0, stop=4, step=1)
일반적으로 .array 속성의 결과는 넘파이 배열을 감싸는 PandasArray
각 데이터를 지칭하는 색인을 지정해 Series 객체를 생성
In [8]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c']) In [9]: obj2 Out[9]: d 4 b 7 a -5 c 3 dtype: int64 In [10]: obj2.index Out[10]: Index(['d', 'b', 'a', 'c'], dtype='object')
넘파이 배열과 비교하면, 단일 값을 선택하거나 여러 값을 선택할 때 색인으로 레이블(라벨, label) 사용 가능
In [11]: obj2['a'] Out[11]: -5 In [12]: obj2['d']=6 In [13]: obj2 Out[13]: d 6 b 7 a -5 c 3 dtype: int64 In [14]: obj2[['c', 'a', 'd']] Out[14]: c 3 a -5 d 6 dtype: int64
불리언 배열을 사용해 값을 걸러내거나 스칼라 곱셈 수행, 수학 함수를 적용하는 등 넘파이 배열 연산을 수행해도 색인과 값 연결은 유지됨
In [15]: obj2[obj2 > 0] Out[15]: d 6 b 7 c 3 dtype: int64 In [16]: obj2 * 2 Out[16]: d 12 b 14 a -10 c 6 dtype: int64 In [17]: import numpy as np In [18]: np.exp(obj2) Out[18]: d 403.428793 b 1096.633158 a 0.006738 c 20.085537 dtype: float64
Series를 이해하는 다른 방법은 고정 길이의 정렬된 딕셔너리라고 생각하는 것, 파이썬의 딕셔너리와 비슷함
파이썬의 딕셔너리가 필요한 곳에 Series 객체를 사용할 수 있음
In [19]: 'b' in obj2 Out[19]: True In [20]: 'e' in obj2 Out[20]: False
파이썬의 딕셔너리에 데이터를 저장해야 한다면 파이썬 딕셔너리 객체로부터 Series 객체를 생성할 수도 있음
In [21]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000} In [22]: obj3 = pd.Series(sdata) In [23]: obj3 Out[23]: Ohio 35000 Texas 71000 Oregon 16000 Utah 5000 dtype: int64
to_dict 메서드를 사용해 Series를 다시 딕셔너리로 변환 가능
In [24]: obj3.to_dict() Out[24]: {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
딕셔너리 객체만 가지고 Series 객체를 생성하면 생성된 Series 객체의 색인은 딕셔너리의 key 메서드에서 반환하는 키의 값 순서대로 들어감, 색인을 직접 지정하고 싶다면 원하는 순서대로 색인을 넘길 수도 있음
In [25]: states = ['California', 'Ohio', 'Oregon', 'Texas'] In [26]: obj4 = pd.Series(sdata, index=states) In [27]: obj4 Out[27]: California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 dtype: float64
위 예제를 보면 sdata에 있는 값 중 3개만 확인할 수 있음, California에 대한 값을 찾을 수 없기 때문 > 위 값은 NaN으로 표시
판다스에서는 누락된 값 또는 NA 값으로 취급
누락된 데이터 지칭할 때 '누락되다', 'NA', 'null' 표현 사용
누락된 데이터 찾을 때 사용하는 isnull, notnull 함수
In [28]: pd.isna(obj4) Out[28]: California True Ohio False Oregon False Texas False dtype: bool In [29]: pd.notna(obj4) Out[29]: California False Ohio True Oregon True Texas True dtype: bool
In [30]: obj4.isna() Out[30]: California True Ohio False Oregon False Texas False dtype: bool
Series의 유용한 기능은 산술 연산에서 색인과 레이블로 자동 정렬하는 기능
In [31]: obj3 Out[31]: Ohio 35000 Texas 71000 Oregon 16000 Utah 5000 dtype: int64 In [32]: obj4 Out[32]: California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 dtype: float64 In [33]: obj3 + obj4 Out[33]: California NaN Ohio 70000.0 Oregon 32000.0 Texas 142000.0 Utah NaN dtype: float64
Series 객체와 색인은 모두 name 속성을 가지고, 이 속성은 판다스의 다른 기능들과 통합되어 있음
In [34]: obj4.name = 'population' In [35]: obj4.index.name = 'state' In [36]: obj4 Out[36]: state California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 Name: population, dtype: float64 # 대입으로 Series의 색인 변경 o In [37]: obj Out[37]: 0 4 1 7 2 -5 3 3 dtype: int64 In [38]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan'] In [39]: obj Out[39]: Bob 4 Steve 7 Jeff -5 Ryan 3 dtype: int64
DataFrame
표 같은 스프레드시트 형식의 자료구조, 여러 개의 열이 있고 서로 다른 종류의 값(숫자, 문자열, 불리언)을 담을 수 있음
행과 열에 대한 색인을 가지며, 색인의 모양이 같은 Series 객체를 담고 있는 파이썬 딕셔너리로 생각하면 됨
가장 흔한 방법은 동일한 길이의 리스트에 담긴 딕셔너리를 이용하거나 넘파이 배열을 이용하는 방법
In [40]: data = {'state':['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], ...: 'year': [2000, 2001, 2002, 2001, 2002, 2003], ...: 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} In [41]: frame = pd.DataFrame(data) In [42]: frame Out[42]: state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 5 Nevada 2003 3.2
큰 DataFrame을 다룰 때는 head 메서드를 이용해 처음 5개 행만 출력, 마지막 5개 행도 출력 가능
In [43]: frame.head() Out[43]: state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 In [44]: frame.tail() Out[44]: state year pop 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 5 Nevada 2003 3.2 In [45]: frame.head(2) Out[45]: state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 In [46]: frame.tail(2) Out[46]: state year pop 4 Nevada 2002 2.9 5 Nevada 2003 3.2
개수 지정이 가능하지만 보통 지정하지 x
columns을 원하는 순서대로 지정하면 해당 순서로 정렬된 DataFrame 객체가 생성됨
In [47]: pd.DataFrame(data, columns=['year', 'state', 'pop']) Out[47]: year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2
딕셔너리에 없는 값을 columns에 넘기면 결과에 결측치(missing value)가 표시
In [48]: frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt']) In [49]: frame2 Out[49]: year state pop debt 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN In [50]: frame2.columns Out[50]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
DataFrame의 열은 Series처럼 딕셔너리 형식의 표기법이나 점 표기법으로 접근할 수 있음
In [51]: frame2['state'] Out[51]: 0 Ohio 1 Ohio 2 Ohio 3 Nevada 4 Nevada 5 Nevada Name: state, dtype: object In [52]: frame2.year Out[52]: 0 2000 1 2001 2 2002 3 2001 4 2002 5 2003 Name: year, dtype: int64
반환된 Series 객체가 DataFrame과 같은 색인을 가지면 알맞은 값으로 name 속성이 채워짐
iloc이나 loc 같은 몇 가지 속성을 사용해 위치나 이름으로 행에 접근할 수 있음
In [53]: frame2.loc[1] Out[53]: year 2001 state Ohio pop 1.7 debt NaN Name: 1, dtype: object In [54]: frame2.iloc[2] Out[54]: year 2002 state Ohio pop 3.6 debt NaN Name: 2, dtype: object In [55]: frame2 Out[55]: year state pop debt 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN
대입으로 열 수정 가능 ex) 현재 비어 있는 debt 열에 스칼라 값이나 배열의 값을 대입할 수 있음
In [56]: frame2['debt'] = 16.5 In [57]: frame2 Out[57]: year state pop debt 0 2000 Ohio 1.5 16.5 1 2001 Ohio 1.7 16.5 2 2002 Ohio 3.6 16.5 3 2001 Nevada 2.4 16.5 4 2002 Nevada 2.9 16.5 5 2003 Nevada 3.2 16.5 In [58]: frame2['debt'] = np.arange(6.) In [59]: frame2 Out[59]: year state pop debt 0 2000 Ohio 1.5 0.0 1 2001 Ohio 1.7 1.0 2 2002 Ohio 3.6 2.0 3 2001 Nevada 2.4 3.0 4 2002 Nevada 2.9 4.0 5 2003 Nevada 3.2 5.0
리스트나 배열을 열에 대입할 때 대입하려는 값의 길이가 DataFrame의 길이와 동일해야 함
Series를 대입하면 DataFrame의 색인에 따라 값이 대입되며 존재하지 않는 색인에는 결측치가 대입됨
In [60]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five']) In [61]: frame2['debt'] = val In [62]: frame2 Out[62]: year state pop debt 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN
존재하지 않는 열을 대입할 경우 새로운 열이 생성됨
파이썬 딕셔너리처럼 del 예약어를 사용해 열 삭제 가능
예시로 state열의 값이 'Ohio'인지 검사한 결과를 불리언 값으로 나타내는 새로운 열(eastern) 만들기
In [63]: frame2['eastern'] = frame2['state'] == 'Ohio' In [64]: frame2 Out[64]: year state pop debt eastern 0 2000 Ohio 1.5 NaN True 1 2001 Ohio 1.7 NaN True 2 2002 Ohio 3.6 NaN True 3 2001 Nevada 2.4 NaN False 4 2002 Nevada 2.9 NaN False 5 2003 Nevada 3.2 NaN False In [65]: frame2['eastern'] Out[65]: 0 True 1 True 2 True 3 False 4 False 5 False Name: eastern, dtype: bool In [66]: frame2.eastern Out[66]: 0 True 1 True 2 True 3 False 4 False 5 False Name: eastern, dtype: bool
del 예약어로 새롭게 만든 열 삭제
In [67]: frame2 Out[67]: year state pop debt eastern 0 2000 Ohio 1.5 NaN True 1 2001 Ohio 1.7 NaN True 2 2002 Ohio 3.6 NaN True 3 2001 Nevada 2.4 NaN False 4 2002 Nevada 2.9 NaN False 5 2003 Nevada 3.2 NaN False In [68]: del frame2['eastern'] In [69]: frame2 Out[69]: year state pop debt 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN In [70]: frame2.columns Out[70]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
DataFrame의 색인을 이용해 얻은 열은 내부 데이터에 대한 뷰일 뿐 복사가 이루어지지 않음
> 이렇게 얻은 Series 객체에 대한 변경은 실제 DataFrame에 반영됨, 복사본이 필요할 때는 Series의 copy 메서드 이용
중첩된 딕셔너리를 이용해 데이터 생성할 수 있음
In [71]: populations = {'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}, ...: 'Nevada': {2001: 2.4, 2002: 2.9}} In [72]: frame3 = pd.DataFrame(populations) In [73]: frame3 Out[73]: Ohio Nevada 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9
중첩된 딕셔너리를 DataFrame에 넘기면 바깥에 있는 딕셔너리의 키가 열이 되고 안에 있는 키는 행이 됨
넘파이 배열과 유사한 문법으로 데이터 전치
In [73]: frame3 Out[73]: Ohio Nevada 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9 In [74]: frame3.T Out[74]: 2000 2001 2002 Ohio 1.5 1.7 3.6 Nevada NaN 2.4 2.9
열의 자료형이 모두 같지 않을 경우에 데이터를 전치하게 되면 이전 자료혀에 대한 정보가 유실될 수 있음
> 그런 경우 열은 순수 파이썬 객체의 배열이 됨
중첩된 딕셔너리를 이용해 DataFrame을 생성하면 안쪽에 있는 딕셔너리 값은 키의 값별로 조합되어 결과의 색인이 됨
색인을 직접 지정하면 지정된 색인으로 DataFrame 생성
In [75]: pd.DataFrame(populations, index=[2001, 2002, 2003]) Out[75]: Ohio Nevada 2001 1.7 2.4 2002 3.6 2.9 2003 NaN NaN In [76]: frame3 Out[76]: Ohio Nevada 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9
Series 객체를 담고 있는 딕셔너리 데이터도 동일한 방식으로 취급됨
In [77]: pdata = {'Ohio': frame3['Ohio'][:-1], ...: 'Nevada': frame3['Nevada'][:2]} In [78]: pd.DataFrame(pdata) Out[78]: Ohio Nevada 2000 1.5 NaN 2001 1.7 2.4
DataFrame의 색인과 열에 name 속성이 설정되어 있다면 이 정보도 함께 출력됨
In [79]: frame3 Out[79]: Ohio Nevada 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9 In [80]: frame3.index.name = 'year' In [81]: frame3.columns.name = 'state' In [82]: frame3 Out[82]: state Ohio Nevada year 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9
Series와 달리 DataFrame에는 name 속성이 없음
DataFrame의 to_numpy 메서드는 DataFrame에 포함된 데이터를 2차원 형태의 ndarray로 반환
In [83]: frame3 Out[83]: state Ohio Nevada year 2000 1.5 NaN 2001 1.7 2.4 2002 3.6 2.9 In [84]: frame3.to_numpy() Out[84]: array([[1.5, nan], [1.7, 2.4], [3.6, 2.9]]) In [85]: frame2 Out[85]: year state pop debt 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN In [87]: frame2.to_numpy() Out[87]: array([[2000, 'Ohio', 1.5, nan], [2001, 'Ohio', 1.7, nan], [2002, 'Ohio', 3.6, nan], [2001, 'Nevada', 2.4, nan], [2002, 'Nevada', 2.9, nan], [2003, 'Nevada', 3.2, nan]], dtype=object)
색인 객체
판다스의 색인 객체는 축 레이블(DataFrame의 열 이름 포함)과 다른 메타데이터(축의 이름 등)를 저장하는 객체
Series나 DataFrame 객체를 생성할 대 사용하는 배열이나 다른 순차적인 레이블은 내부적으로 색인으로 변환됨
In [88]: obj = pd.Series(np.arange(3), index=['a', 'b', 'c']) In [89]: index = obj.index In [90]: index Out[90]: Index(['a', 'b', 'c'], dtype='object') In [91]: index[1:] Out[91]: Index(['b', 'c'], dtype='object') # 색인 객체 변경 불가능 In [92]: index[1] = 'd' # TypeError --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[92], line 1 ----> 1 index[1] = 'd' File ~\anaconda3\Lib\site-packages\pandas\core\indexes\base.py:5157, in Index.__setitem__(self, key, value) 5155 @final 5156 def __setitem__(self, key, value): -> 5157 raise TypeError("Index does not support mutable operations") TypeError: Index does not support mutable operations
불변성 덕분에 자료구조 사이에서 색인을 안전하게 공유 가능
In [93]: labels = pd.Index(np.arange(3)) In [94]: labels Out[94]: Index([0, 1, 2], dtype='int32') In [95]: obj2 = pd.Series([1.5, -2.5, 0], index=labels) In [96]: obj2 Out[96]: 0 1.5 1 -2.5 2 0.0 dtype: float64 In [97]: obj2.index is labels Out[97]: True
배열과 유사하게 Index 객체도 고정된 크기로 작동
In [99]: frame3.columns Out[99]: Index(['Ohio', 'Nevada'], dtype='object', name='state') In [100]: 'Ohio' in frame3.columns Out[100]: True In [101]: 2003 in frame3.index Out[101]: False
파이썬의 집합과는 달리 판다스의 색인은 중복되는 값 허용
In [102]: pd.Index(['foo', 'foo', 'bar', 'bar']) Out[102]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')
핵심 기능
재색인
판다스 객체의 중요한 기능 중 하나 reindex, 새로운 색인에 적합하도록 객체를 새로 생성하는 기능
In [103]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c']) In [104]: obj Out[104]: d 4.5 b 7.2 a -5.3 c 3.6 dtype: float64 # reindex를 호출하면 데이터를 새로운 색인에 맞게 재배열하고, # 존재하지 않는 색인값이 있다면 비어 있는 값(NaN)을 새로 추가함 In [105]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e']) In [106]: obj2 Out[106]: a -5.3 b 7.2 c 3.6 d 4.5 e NaN dtype: float64
값을 할당하지 않아도 코드는 실행되지만, 값을 사용하려면 할당해서 써야 함
시계열 같은 순차적인 데이터를 재색인할 때, 값을 보간하거나 채워 넣어야 할 경우, method 옵션을 이용해 해결할 수 있으며
ffill 같은 메서드를 이용해 누락된 값을 직전의 값으로 채워 넣을 수 있음
In [107]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4]) In [108]: obj3 Out[108]: 0 blue 2 purple 4 yellow dtype: object In [109]: obj3.reindex(np.arange(6), method='ffill') Out[109]: 0 blue 1 blue 2 purple 3 purple 4 yellow 5 yellow dtype: object
DataFrame에 대한 reindex는 행(색인), 열 또는 둘 다 변경 가능, 순서만 전달하면 행이 재색인됨
In [110]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), ...: index=['a', 'c', 'd'], ...: columns=['Ohio', 'Texas', 'California']) In [111]: frame Out[111]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 In [112]: frame2 = frame.reindex(index=['a', 'b', 'c', 'd']) In [113]: frame2 Out[113]: Ohio Texas California a 0.0 1.0 2.0 b NaN NaN NaN c 3.0 4.0 5.0 d 6.0 7.0 8.0
열은 columns 예약어를 사용해 재색인
In [111]: frame Out[111]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 In [114]: states = ['Texas', 'Utah', 'California'] In [115]: frame.reindex(columns=states) Out[115]: Texas Utah California a 1 NaN 2 c 4 NaN 5 d 7 NaN 8 # 다른 방법은 새로운 축 레이블을 인수로 넘긴 후 axis 키워드를 사용해 재색인할 축을 지정하는 것 In [118]: frame Out[118]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 In [117]: frame.reindex(states, axis='columns') Out[117]: Texas Utah California a 1 NaN 2 c 4 NaN 5 d 7 NaN 8
+ loc 연산자를 이용해 재색인할 수 있음, 이 방법은 모든 새로운 색인 레이블이 DataFrame에 존재하는 경우에만 작동
(reindex 함수는 새로운 레이블에 대한 결측치 삽입)
In [120]: frame Out[120]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 In [121]: frame.loc[ ['a', 'd', 'c'] ] # 인덱스(행축)만 재색인 Out[121]: Ohio Texas California a 0 1 2 d 6 7 8 c 3 4 5 In [122]: frame.loc[['a', 'd', 'c'], ['California', 'Texas']] Out[122]: California Texas a 2 1 d 8 7 c 5 4
하나의 행이나 열 삭제
삭제하려는 축이 제외된 리스트를 이미 가지고 있거나, 색인 배열을 갖고 있다면 reindex 메서드나 .loc 기반의 색인을 이용할 수 있으므로 행이나 열을 쉽게 삭제할 수 있으나 이 방법은 데이터의 모양을 변경하는 작업 필요
drop 메서드를 사용하면 선택한 값들이 삭제된 새로운 객체를 얻을 수 있음
In [123]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e']) In [124]: obj Out[124]: a 0.0 b 1.0 c 2.0 d 3.0 e 4.0 dtype: float64 In [125]: new_obj = obj.drop('c') In [126]: new_obj Out[126]: a 0.0 b 1.0 d 3.0 e 4.0 dtype: float64 In [127]: obj.drop(['d', 'c']) Out[127]: a 0.0 b 1.0 e 4.0 dtype: float64
DataFrame에서는 행과 열 모두에서 색인값을 삭제할 수 있음
In [128]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), ...: index=['Ohio', 'Colorado', 'Utah', 'New York'], ...: columns=['one', 'two', 'three', 'four']) In [129]: data Out[129]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15
drop 함수 인수로 삭제하고 싶은 행 이름을 넘기면 해당 행의 값 모두 삭제, 열의 레이블을 삭제하려면 columns 키워드 사용
In [130]: data.drop(index=['Colorado', 'Ohio']) Out[130]: one two three four Utah 8 9 10 11 New York 12 13 14 15 In [131]: data.drop(columns=['two']) Out[131]: one three four Ohio 0 2 3 Colorado 4 6 7 Utah 8 10 11 New York 12 14 15
열의 값을 삭제할 때는 넘파이처럼 axis=1 또는 axis='columns'를 인수로 넘겨주면 됨
In [132]: data.drop('two', axis=1) Out[132]: one three four Ohio 0 2 3 Colorado 4 6 7 Utah 8 10 11 New York 12 14 15 In [133]: data.drop(['two', 'four'], axis='columns') Out[133]: one three Ohio 0 2 Colorado 4 6 Utah 8 10 New York 12 14
색인하기, 선택하기, 거르기
Series의 색인(obj[...])은 넘파이 배열의 색인과 유사하게 작동하지만 정수가 아니어도 된다는 점이 다름
In [135]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd']) In [136]: obj Out[136]: a 0.0 b 1.0 c 2.0 d 3.0 dtype: float64 In [137]: obj['b'] Out[137]: 1.0 In [138]: obj[1] Out[138]: 1.0 In [139]: obj[2:4] Out[139]: c 2.0 d 3.0 dtype: float64 In [140]: obj[['b', 'a', 'd']] Out[140]: b 1.0 a 0.0 d 3.0 dtype: float64 In [141]: obj[[1, 3]] Out[141]: b 1.0 d 3.0 dtype: float64 In [142]: obj[obj < 2] Out[142]: a 0.0 b 1.0 dtype: float64
특수 연산자인 loc을 이용하는 방식이 더 선호됨
In [143]: obj.loc[['b', 'a', 'd']] Out[143]: b 1.0 a 0.0 d 3.0 dtype: float64
[]로 선택하는 경우 정수의 처리 방식이 다르기 때문에 loc를 선호
일반적으로 [] 기반의 색인은 정수가 포함될 경우 이를 레이블로 인식, 색인의 자료형에 따라 작동 방식이 달라짐
In [144]: obj1 = pd.Series([1, 2, 3], index=[2, 0, 1]) In [145]: obj2 = pd.Series([1, 2, 3], index=['a', 'b', 'c']) In [146]: obj1 Out[146]: 2 1 0 2 1 3 dtype: int64 In [147]: obj2 Out[147]: a 1 b 2 c 3 dtype: int64 In [148]: obj1[[0, 1, 2]] Out[148]: 0 2 1 3 2 1 dtype: int64 In [149]: obj2[[0, 1, 2]] Out[149]: a 1 b 2 c 3 dtype: int64 In [150]: obj1, obj2 Out[150]: (2 1 0 2 1 3 dtype: int64, a 1 b 2 c 3 dtype: int64) In [151]: obj2 Out[151]: a 1 b 2 c 3 dtype: int64 In [152]: obj2[ [2, 0, 1] ] Out[152]: c 3 a 1 b 2 dtype: int64
loc를 이용하면 색인에 정수가 포함되어 있지 않을 경우 obj.loc[[0, 1, 2]] 구문은 실패
In [153]: obj2.loc[[0, 1, 2]] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) Cell In[153], line 1 ----> 1 obj2.loc[[0, 1, 2]] KeyError: "None of [Index([0, 1, 2], dtype='int32')] are in the [index]"
loc 연산자는 레이블로만 색인을 취함
색인이 정수를 포함하고 있는지 여부와 관계없이 일관되게 작동하도록 정수로만 색인을 취하는 iloc 연산자도 존재
In [154]: obj2.loc[['a', 'b', 'c']] Out[154]: a 1 b 2 c 3 dtype: int64 In [155]: obj1.iloc[[0, 1, 2]] Out[155]: 2 1 0 2 1 3 dtype: int64 In [156]: obj2.iloc[[0, 1, 2]] Out[156]: a 1 b 2 c 3 dtype: int64
색인으로 DataFrame에서 하나 이사으이 열의 값을 가져올 수 있음
In [157]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), ...: index=['Ohio', 'Colorado', 'Utah', 'New York'], ...: columns=['one', 'two', 'three', 'four']) In [158]: data Out[158]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 In [159]: data['two'] Out[159]: Ohio 1 Colorado 5 Utah 9 New York 13 Name: two, dtype: int32 In [160]: data[['three', 'one']] Out[160]: three one Ohio 2 0 Colorado 6 4 Utah 10 8 New York 14 12 In [162]: pd.DataFrame(data, columns=['two']) Out[162]: two Ohio 1 Colorado 5 Utah 9 New York 13 # 'Ohio' 가지고 오고 싶은 경우 In [163]: data.loc['Ohio'] Out[163]: one 0 two 1 three 2 four 3 Name: Ohio, dtype: int32 In [164]: data.iloc[0] Out[164]: one 0 two 1 three 2 four 3 Name: Ohio, dtype: int32
슬라이싱으로 행을 선택하거나 불리언 배열로 행을 선택할 수 있음
In [165]: data[:2] Out[165]: one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 In [166]: data[data['three'] > 5] Out[166]: one two three four Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15
또 다른 방법으로는 스칼라 비교를 통해 생성된 불리언 DataFrame을 사용해 값을 선택하는 것
In [167]: data < 5 Out[167]: one two three four Ohio True True True True Colorado True False False False Utah False False False False New York False False False False In [168]: data[data < 5] = 0 In [169]: data Out[169]: one two three four Ohio 0 0 0 0 Colorado 0 5 6 7 Utah 8 9 10 11 New York 12 13 14 15
DataFrame을 사용해 True 값을 가진 위치에 0을 할당할 수 있음
loc과 iloc로 선택
Series와 마찬가지로 DataFrame에는 레이블과 정수 기반 색인을 위한 loc, iloc 속성이 존재
DataFrame은 2차원이므로 축 레이블(loc) 또는 정수 색인(iloc)을 이용해 행렬의 부분집합 선택
In [173]: data Out[173]: one two three four Ohio 0 0 0 0 Colorado 0 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 In [174]: data.loc['Colorado'] Out[174]: one 0 two 5 three 6 four 7 Name: Colorado, dtype: int32 In [175]: data.loc[['Colorado', 'New York']] Out[175]: one two three four Colorado 0 5 6 7 New York 12 13 14 15 In [176]: data.loc['Colorado', ['two', 'three']] Out[176]: two 5 three 6 Name: Colorado, dtype: int32
행과 열 조합해 선택 가능
In [177]: data Out[177]: one two three four Ohio 0 0 0 0 Colorado 0 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 In [178]: data.iloc[2] Out[178]: one 8 two 9 three 10 four 11 Name: Utah, dtype: int32 In [179]: data.iloc[[2, 1]] Out[179]: one two three four Utah 8 9 10 11 Colorado 0 5 6 7 In [180]: data.iloc[2, [3, 0, 1]] Out[180]: four 11 one 8 two 9 Name: Utah, dtype: int32 In [181]: data.iloc[[1, 2], [3, 0, 1]] Out[181]: four one two Colorado 7 0 5 Utah 11 8 9
두 색인 함수는 슬라이스도 지원, 단일 레이블이나 레이블 리스트도 지원함
In [182]: data.loc[:'Utah', 'two'] Out[182]: Ohio 0 Colorado 5 Utah 9 Name: two, dtype: int32 In [183]: data.iloc[:, :3][data.three > 5] Out[183]: one two three Colorado 0 5 6 Utah 8 9 10 New York 12 13 14
loc에는 불리언 배열을 사용할 수 있지만 iloc에서는 사용할 수 없음
In [184]: data.loc[data.three >= 2] Out[184]: one two three four Colorado 0 5 6 7 Utah 8 9 10 11 New York 12 13 14 15
정수 색인의 함정
정수 색인으로 판다스 객체를 다루다 보면 리스트나 튜플 같은 파이썬 내장 자료구조에서 색인을 다루는 방법과의 차이점 때문에 실수하는 경우 발생
In [185]: ser = pd.Series(np.arange(3.)) In [186]: ser Out[186]: 0 0.0 1 1.0 2 2.0 dtype: float64 In [188]: ser[1:2] Out[188]: 1 1.0 dtype: float64 In [189]: ser[1] Out[189]: 1.0 In [187]: ser[-1] --------------------------------------------------------------------------- ValueError Traceback (most recent call last) File ~\anaconda3\Lib\site-packages\pandas\core\indexes\range.py:345, in RangeIndex.get_loc(self, key) 344 try: --> 345 return self._range.index(new_key) 346 except ValueError as err: ValueError: -1 is not in range The above exception was the direct cause of the following exception: KeyError Traceback (most recent call last) Cell In[187], line 1 ----> 1 ser[-1] File ~\anaconda3\Lib\site-packages\pandas\core\series.py:1007, in Series.__getitem__(self, key) 1004 return self._values[key] 1006 elif key_is_scalar: -> 1007 return self._get_value(key) 1009 if is_hashable(key): 1010 # Otherwise index.get_value will raise InvalidIndexError 1011 try: 1012 # For labels that don't resolve as scalars like tuples and frozensets File ~\anaconda3\Lib\site-packages\pandas\core\series.py:1116, in Series._get_value(self, label, takeable) 1113 return self._values[label] 1115 # Similar to Index.get_value, but we do not fall back to positional -> 1116 loc = self.index.get_loc(label) 1118 if is_integer(loc): 1119 return self._values[loc] File ~\anaconda3\Lib\site-packages\pandas\core\indexes\range.py:347, in RangeIndex.get_loc(self, key) 345 return self._range.index(new_key) 346 except ValueError as err: --> 347 raise KeyError(key) from err 348 if isinstance(key, Hashable): 349 raise KeyError(key) KeyError: -1
판다스는 레이블 색인을 찾는 데 실패해 정수 색인으로 값을 찾음
레이블 색인이 0, 1, 2를 포함하는 경우 파이썬은 사용자가 레이블 색인으로 선택하고 싶은 건지, 정수 색인으로 선택하고 싶은 건지 추측하기 쉽지 않음
In [190]: ser Out[190]: 0 0.0 1 1.0 2 2.0 dtype: float64 In [191]: ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c']) In [192]: ser2[-1] Out[192]: 2.0 In [193]: ser2.iloc[-1] Out[193]: 2.0
레이블에 대해 loc, 정수 색인에 대해 iloc를 사용하면 원하던 값을 얻을 수 있음
항상 loc, iloc 사용해 모호함 피할 것
산술 연산과 데이터 정렬
서로 다른 인덱스를 가지고 있는 객체 간의 산술 연산 간단하게 처리 가능
객체를 더할 때 짝이 맞지 않는 인덱스가 있다면 결과에 두 색인이 통합됨
In [46]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e']) In [47]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], ...: index=['a', 'c', 'e', 'f', 'g']) In [48]: s1 Out[48]: a 7.3 c -2.5 d 3.4 e 1.5 dtype: float64 In [49]: s2 Out[49]: a -2.1 c 3.6 e -1.5 f 4.0 g 3.1 dtype: float64 In [50]: s1 + s2 Out[50]: a 5.2 c 1.1 d NaN e 0.0 f NaN g NaN dtype: float64 # 서로 겹치는 인덱스가 없는 경우 데이터는 결측치가 됨 # 추후 산술 연산 시 누락된 값 전달되며 DataFrame의 경우 행과 열 모두에 정렬 적용 In [51]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), ...: index=['Ohio', 'Texas', 'Colorado']) In [52]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), ...: index=['Utah', 'Ohio', 'Texas', 'Oregon']) In [53]: df1 Out[53]: b c d Ohio 0.0 1.0 2.0 Texas 3.0 4.0 5.0 Colorado 6.0 7.0 8.0 In [54]: df2 Out[54]: b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 In [55]: df1 + df2 Out[55]: b c d e Colorado NaN NaN NaN NaN Ohio 3.0 NaN 6.0 NaN Oregon NaN NaN NaN NaN Texas 9.0 NaN 12.0 NaN Utah NaN NaN NaN NaN
공통 열이나 행 레이블이 없는 DataFrame을 더하면 결과는 아무것도 나타나지 않음
In [58]: df1 Out[58]: A 0 1 1 2 In [59]: df2 Out[59]: B 0 3 1 4 In [60]: df1 + df2 Out[60]: A B 0 NaN NaN 1 NaN NaN
산술 연산 메서드에 채워 넣을 값 지정
색인이 서로 다른 객체 간의 산술 연산에서 존재하지 않는 축의 값을 0같이 특수한 값으로 지정하고 싶은 경우
np.nan을 대입해 NA로 설정
In [62]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), ...: columns=list('abcd')) In [63]: df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), ...: columns=list('abcde')) In [64]: df2.loc[1, 'b'] = np.nan In [65]: df1 Out[65]: a b c d 0 0.0 1.0 2.0 3.0 1 4.0 5.0 6.0 7.0 2 8.0 9.0 10.0 11.0 In [66]: df2 Out[66]: a b c d e 0 0.0 1.0 2.0 3.0 4.0 1 5.0 NaN 7.0 8.0 9.0 2 10.0 11.0 12.0 13.0 14.0 3 15.0 16.0 17.0 18.0 19.0 In [67]: df1 + df2 Out[67]: a b c d e 0 0.0 2.0 4.0 6.0 NaN 1 9.0 NaN 13.0 15.0 NaN 2 18.0 20.0 22.0 24.0 NaN 3 NaN NaN NaN NaN NaN
df1에 add 메서드를 사용해 df2와 fill_value 값을 인수로 전달
연산에서 누락된 값은 fill_value 값으로 대체
In [68]: df1.add(df2, fill_value=0) Out[68]: a b c d e 0 0.0 2.0 4.0 6.0 4.0 1 9.0 5.0 13.0 15.0 9.0 2 18.0 20.0 22.0 24.0 14.0 3 15.0 16.0 17.0 18.0 19.0
각각의 산술 연산 메서드는 r로 시작하는 계산 인수를 뒤집어 계산하는 짝꿍 메서드를 가짐
두 코드의 계산 결과는 같음
In [69]: 1 / df1 Out[69]: a b c d 0 inf 1.000000 0.500000 0.333333 1 0.250 0.200000 0.166667 0.142857 2 0.125 0.111111 0.100000 0.090909 In [70]: df1.rdiv(1) Out[70]: a b c d 0 inf 1.000000 0.500000 0.333333 1 0.250 0.200000 0.166667 0.142857 2 0.125 0.111111 0.100000 0.090909
Series나 DataFrame을 재색인할 때도 fill_value 지정 가능
In [71]: df1 Out[71]: a b c d 0 0.0 1.0 2.0 3.0 1 4.0 5.0 6.0 7.0 2 8.0 9.0 10.0 11.0 In [72]: df2 Out[72]: a b c d e 0 0.0 1.0 2.0 3.0 4.0 1 5.0 NaN 7.0 8.0 9.0 2 10.0 11.0 12.0 13.0 14.0 3 15.0 16.0 17.0 18.0 19.0 In [73]: df1.reindex(columns=df2.columns, fill_value=0) Out[73]: a b c d e 0 0.0 1.0 2.0 3.0 0 1 4.0 5.0 6.0 7.0 0 2 8.0 9.0 10.0 11.0 0 In [74]: df1.reindex(columns=df2.columns) Out[74]: a b c d e 0 0.0 1.0 2.0 3.0 NaN 1 4.0 5.0 6.0 7.0 NaN 2 8.0 9.0 10.0 11.0 NaN
DataFrame과 Series 간의 연산
In [75]: arr = np.arange(12.).reshape((3, 4)) In [76]: arr Out[76]: array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]) In [77]: arr[0] Out[77]: array([0., 1., 2., 3.]) In [78]: arr - arr[0] Out[78]: array([[0., 0., 0., 0.], [4., 4., 4., 4.], [8., 8., 8., 8.]]) In [79]: arr1 = np.arange(12.).reshape((3, 4)) In [80]: arr1 Out[80]: array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]) In [81]: arr2 = np.array( [1, 2, 3, 4] ) In [82]: arr2 Out[82]: array([1, 2, 3, 4]) In [83]: arr1- arr2 Out[83]: array([[-1., -1., -1., -1.], [ 3., 3., 3., 3.], [ 7., 7., 7., 7.]]) In [84]: arr = np.arange(12.).reshape((3, 4)) In [85]: arr Out[85]: array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]) In [86]: arr[0] Out[86]: array([0., 1., 2., 3.]) In [87]: arr - arr[0] Out[87]: array([[0., 0., 0., 0.], [4., 4., 4., 4.], [8., 8., 8., 8.]]) In [88]: arr[0][:] Out[88]: array([0., 1., 2., 3.])
arr에서 arr[0]을 빼면 계산은 각 행에 대해 한 번씩만 수행되며 브로드캐스팅(broadcasting)이라 부름
DataFrame과 Series 간의 연산은 이와 유사
In [90]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), ...: columns=list('bde'), ...: index=['Utah', 'Ohio', 'Texas', 'Oregon']) In [91]: series = frame.iloc[0] In [92]: frame Out[92]: b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 In [93]: series Out[93]: b 0.0 d 1.0 e 2.0 Name: Utah, dtype: float64
기본적으로 DataFrame과 Series 간의 산술 연산은 Series의 색인을 DataFrame의 열에 맞추고 아래 행으로 브로드캐스팅
In [94]: frame - series Out[94]: b d e Utah 0.0 0.0 0.0 Ohio 3.0 3.0 3.0 Texas 6.0 6.0 6.0 Oregon 9.0 9.0 9.0
만약 색인값을 DataFrame의 열이나 Series의 색인에서 찾을 수 없다면 그 객체는 형식을 맞추기 위해 재색인됨
In [95]: series2 = pd.Series(np.arange(3), index=['b', 'e', 'f']) In [96]: series2 Out[96]: b 0 e 1 f 2 dtype: int32 In [97]: frame + series2 Out[97]: b d e f Utah 0.0 NaN 3.0 NaN Ohio 3.0 NaN 6.0 NaN Texas 6.0 NaN 9.0 NaN Oregon 9.0 NaN 12.0 NaN
브로드캐스팅 대신 각 행에 대해 연산을 수행하고 싶다면 산술 연산 메서드를 사용해 색인이 일치하도록 지정
In [98]: series3 = frame['d'] In [99]: frame Out[99]: b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 In [100]: series3 Out[100]: Utah 1.0 Ohio 4.0 Texas 7.0 Oregon 10.0 Name: d, dtype: float64 In [101]: frame.sub(series3, axis='index') Out[101]: b d e Utah -1.0 0.0 1.0 Ohio -1.0 0.0 1.0 Texas -1.0 0.0 1.0 Oregon -1.0 0.0 1.0
인수로 넘기는 axis 값은 연산을 적용할 축 번호, 예제 코드에서 axis='index'는 DataFrame의 열을 따라 연산을 수행하라는 의미
함수 적용과 매핑
판다스 객체에도 넘파이의 유니버설 함수(배열의 각 원소에 적용되는 메서드)를 적용할 수 있음
In [102]: frame = pd.DataFrame(np.random.standard_normal((4, 3)), ...: columns=list('bde'), ...: index=['Utah', 'Ohio', 'Texas', 'Oregon']) In [103]: frame Out[103]: b d e Utah 0.570715 -1.009360 -0.759693 Ohio -0.716131 -0.455551 1.013025 Texas -0.482878 -1.846770 -0.492987 Oregon 1.276539 0.185498 -0.273742 In [104]: np.abs(frame) Out[104]: b d e Utah 0.570715 1.009360 0.759693 Ohio 0.716131 0.455551 1.013025 Texas 0.482878 1.846770 0.492987 Oregon 1.276539 0.185498 0.273742
절대값 구하는 abs 적용
자주 사용하는 연산은 각 행이나 열의 1차원 배열에 함수 적용하는 것
DataFrame의 apply 메서드
In [106]: def f1(x): ...: return x.max() - x.min() ...: In [107]: frame.apply(f1) Out[107]: b 1.992670 d 2.032268 e 1.772718 dtype: float64 In [108]: frame.apply(f1, axis='columns') Out[108]: Utah 1.580075 Ohio 1.729156 Texas 1.363892 Oregon 1.550281 dtype: float64
apply 함수에서 axis='columns' 인수를 넘기면 각 행에 대해 한 번씩만 수행됨, '열을 따라 적용된다'라고 이해할 것
합계(sum)나 평균(mean)은 DataFrame의 메서드로 존재하므로 apply 메서드를 사용해야만 하는 것은 아님
apply 메서드에 전달된 함수는 스칼라 값을 반환할 필요 없음
> 여러 값을 가진 Series를 반환해도 됨
In [109]: def f2(x): ...: return pd.Series([x.min(), x.max()], index=['min', 'max']) ...: In [110]: frame.apply(f2) Out[110]: b d e min -0.716131 -1.846770 -0.759693 max 1.276539 0.185498 1.013025
배열의 각 원소에 적용되는 파이썬 함수를 사용할 수 있음
frame 객체에서 부동소수점을 문자열 포맷으로 변환하고 싶은 경우 applymap을 이용해 수행 가능
In [111]: def my_format(x): ...: return f"{x:.2f}" ...: In [112]: frame.applymap(my_format) Out[112]: b d e Utah 0.57 -1.01 -0.76 Ohio -0.72 -0.46 1.01 Texas -0.48 -1.85 -0.49 Oregon 1.28 0.19 -0.27 In [113]: frame['e'] Out[113]: Utah -0.759693 Ohio 1.013025 Texas -0.492987 Oregon -0.273742 Name: e, dtype: float64 In [114]: frame['e'].map(my_format) Out[114]: Utah -0.76 Ohio 1.01 Texas -0.49 Oregon -0.27 Name: e, dtype: object
메서드 이름이 applymap인 이유는 Series가 각 원소에 적용할 함수를 지정하기 위한 map 메서드를 갖기 때문
정렬과 순위
정렬된 새로운 객체를 반환하는 sort_index 메서드를 사용해 행과 열의 인덱스를 알파벳순으로 정렬할 수 있음
In [115]: obj = pd.Series(np.arange(4), index=['d', 'a', 'b', 'c']) In [116]: obj Out[116]: d 0 a 1 b 2 c 3 dtype: int32 In [117]: obj.sort_index() Out[117]: a 1 b 2 c 3 d 0 dtype: int32
DataFrame은 행과 열 중 하나의 인덱스를 기준으로 정렬할 수 있음
In [118]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)), ...: index=['three', 'one'], ...: columns=['d', 'a', 'b', 'c']) In [119]: frame Out[119]: d a b c three 0 1 2 3 one 4 5 6 7 In [120]: frame.sort_index() Out[120]: d a b c one 4 5 6 7 three 0 1 2 3 In [121]: frame.sort_index(axis='columns') Out[121]: a b c d three 1 2 3 0 one 5 6 7 4
데이터는 기본적으로 오름차순 정렬, 내림차순으로도 정렬할 수 있음
In [122]: frame.sort_index(axis='columns', ascending=False) Out[122]: d c b a three 0 3 2 1 one 4 7 6 5
Series 객체를 값에 따라 정렬: sort_values
In [123]: obj = pd.Series([4, 7, -3, 2]) In [124]: obj.sort_values() Out[124]: 2 -3 3 2 0 4 1 7 dtype: int64 # 정렬할 때 누락된 값(결측치)은 기본적으로 Series 객체의 가장 마지막에 위치 In [125]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) In [126]: obj.sort_values() Out[126]: 4 -3.0 5 2.0 0 4.0 2 7.0 1 NaN 3 NaN dtype: float64 # + asc False 적용 가능 In [128]: obj = pd.Series([4, 7, -3, 2]) In [129]: obj.sort_values() Out[129]: 2 -3 3 2 0 4 1 7 dtype: int64 In [130]: obj.sort_values(ascending=False) Out[130]: 1 7 0 4 3 2 2 -3 dtype: int64
# na_position 옵션을 사용해 누락된 값을 먼저 정렬할 수 있음 In [131]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) In [132]: obj.sort_values(na_position='first') Out[132]: 1 NaN 3 NaN 4 -3.0 5 2.0 0 4.0 2 7.0 dtype: float64 # DataFrame에서 하나 이상의 열에 있는 값으로 정렬하는 경우 필요한 열의 이름을 넘기기 In [133]: frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]}) In [134]: frame Out[134]: b a 0 4 0 1 7 1 2 -3 0 3 2 1 In [135]: frame.sort_values('b') Out[135]: b a 2 -3 0 3 2 1 0 4 0 1 7 1 # 여러 개의 열을 정렬할 때는 열 이름 리스트 넘기기 In [136]: frame.sort_values(['a', 'b']) Out[136]: b a 2 -3 0 0 4 0 3 2 1 1 7 1
리스트 아닌 튜플에서는 에러
순위는 가장 낮은 값부터 시작해 배열의 유효한 데이터 개수까지의 순서를 매김
기본적으로 Series와 DataFrame의 rank 메서드는 동점인 항목에 대해서는 평균 순위를 매겨 책정
In [137]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) In [138]: obj.rank() Out[138]: 0 6.5 1 1.0 2 6.5 3 4.5 4 3.0 5 2.0 6 4.5 dtype: float64 # 데이터상에서 나타나는 순서에 따라 순위를 매길 수 있음 In [139]: obj.rank(method='first') Out[139]: 0 6.0 1 1.0 2 7.0 3 4.0 4 3.0 5 2.0 6 5.0 dtype: float64 # 0번째, 2번째 인덱스에 대해 평균 순위인 6.5를 적용하는 대신 먼저 출현한 순서대로 # 6과 7 적용 # 내림차순 In [140]: obj.rank(ascending=False) Out[140]: 0 1.5 1 7.0 2 1.5 3 3.5 4 5.0 5 6.0 6 3.5 dtype: float64
DataFrame에서는 행과 열에 대해 순위를 정할 수 있음
In [141]: frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], ...: 'c': [-2, 5, 8, -2.5]}) In [142]: frame Out[142]: b a c 0 4.3 0 -2.0 1 7.0 1 5.0 2 -3.0 0 8.0 3 2.0 1 -2.5 In [143]: frame.rank(axis='columns') Out[143]: b a c 0 3.0 2.0 1.0 1 3.0 1.0 2.0 2 1.0 2.0 3.0 3 3.0 2.0 1.0
중복 색인
판다스의 많은 함수(reindex 같은)에서 색인값은 유일해야 하지만 의무는 아님
중복된 색인값을 갖는 Series 객체
In [153]: obj = pd.Series(np.arange(5), index=['a', 'a', 'b', 'b', 'c']) In [154]: obj Out[154]: a 0 a 1 b 2 b 3 c 4 dtype: int32 In [155]: # is_unique 속성은 해당 값이 유일한지 아닌지 알려 줌 In [156]: obj.index.is_unique Out[156]: False # 유일한 색인만 있다면 스칼라 값 반환, 중복되는 색인값 있을 때는 하나의 Series 객체 반환 In [157]: obj['a'] Out[157]: a 0 a 1 dtype: int32 In [158]: obj['c'] Out[158]: 4 # 레이블의 반복 여부에 따라 색인을 이용해 선택한 결과가 다를 수 있기 때문에 # 코드가 복잡해질 수 있음 In [159]: df = pd.DataFrame(np.random.standard_normal((5, 3)), ...: index=['a', 'a', 'b', 'b', 'c']) In [160]: df Out[160]: 0 1 2 a 0.391695 -0.442054 -0.033109 a 0.219205 -0.241442 -0.032905 b 1.263946 1.230159 1.335586 b -0.775331 -0.474710 0.230852 c -0.503077 0.062859 0.881807 In [161]: df.loc['b'] Out[161]: 0 1 2 b 1.263946 1.230159 1.335586 b -0.775331 -0.474710 0.230852 In [162]: df.loc['c'] Out[162]: 0 -0.503077 1 0.062859 2 0.881807 Name: c, dtype: float64
기술 통계 계산과 요약
판다스 객체는 일반적인 수학 메서드와 통계 메서드를 갖고 있음
메서드 대부분은 하나의 Series나 DataFrame의 행과 열에서 단일 값(합, 평균 같은)을 구하는 축소(reduction) 혹은 요약 통계(summary statistics) 범주에 속함
순수 넘파이 배열에서 제공하는 동일 메서드와는 다르게 판다스 메서드는 처음부터 누락된 데이터를 제외하도록 설계
In [163]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5], ...: [np.nan, np.nan], [0.75, -1.3]], ...: index=['a', 'b', 'c', 'd'], ...: columns=['one', 'two']) In [164]: df Out[164]: one two a 1.40 NaN b 7.10 -4.5 c NaN NaN d 0.75 -1.3
In [165]: df.sum() Out[165]: one 9.25 two -5.80 dtype: float64 # axis='columns' 혹은 axis=1 옵션을 넘기면 각 열의 합 반환 In [166]: df.sum(axis='columns') Out[166]: a 1.40 b 2.60 c 0.00 d -0.55 dtype: float64
전체 행과 열의 모든 값이 NA 값이라면 그 합은 0이 되고 값이 하나라도 NA라면 결괏값은 NA가 됨
> skipna 옵션으로 비활성화할 수 있으며 이 경우 행과 열의 NA 값은 해당 결과를 NA로 지정
In [169]: df.sum(axis='index', skipna=False) # 결측치 포함 Out[169]: one NaN two NaN dtype: float64 In [170]: df.sum(axis='columns', skipna=False) # 결측치 포함 Out[170]: a NaN b 2.60 c NaN d -0.55 dtype: float64
평균 같은 일부 집계에는 결괏값을 생성하기 위해 최소 하나 이상의 NA가 아닌 값 필요
In [171]: df.mean(axis='columns') Out[171]: a 1.400 b 1.300 c NaN d -0.275 dtype: float64 In [172]: df.mean(axis='index') Out[172]: one 3.083333 two -2.900000 dtype: float64
idxmin이나 idxmax 같은 메서드는 최소 혹은 최댓값을 가진 색인값 같은 간접 통계(indirect statistics) 반환
In [173]: df Out[173]: one two a 1.40 NaN b 7.10 -4.5 c NaN NaN d 0.75 -1.3 In [174]: df.idxmin() Out[174]: one d two b dtype: object In [175]: df.idxmax() Out[175]: one b two d dtype: object # 누산(accumulation) In [176]: df.cumsum() Out[176]: one two a 1.40 NaN b 8.50 -4.5 c NaN NaN d 9.25 -5.8 # 한 번에 여러 개의 요약 통계를 만드는 describe 메서드 In [177]: df.describe() Out[177]: one two count 3.000000 2.000000 mean 3.083333 -2.900000 std 3.493685 2.262742 min 0.750000 -4.500000 25% 1.075000 -3.700000 50% 1.400000 -2.900000 75% 4.250000 -2.100000 max 7.100000 -1.300000 # 수치 데이터가 아닐 경우 describe는 다른 요약 통계 생성 In [178]: obj = pd.Series(['a', 'a', 'b', 'c'] * 4) In [179]: obj.describe() Out[179]: count 16 unique 3 top a freq 8 dtype: object
유일값, 값 세기, 멤버십
In [11]: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c']) In [12]: uniques = obj.unique() In [13]: uniques Out[13]: array(['c', 'a', 'd', 'b'], dtype=object)
unique 함수는 Series에서 중복되는 값 제거하고 유일한 값(unique value)만 담은 Series 반환
정렬된 상태로 반환되지 않음, uniques.sort()를 이용해 나중에 정렬 가능
sort 메서드는 원본 변경함
In [14]: obj.value_counts() # 도수(데이터의 개수) 반환 Out[14]: c 3 a 3 b 2 d 1 Name: count, dtype: int64 In [15]: pd.value_counts(obj.to_numpy(), sort=False) # 위와 같은 코드 Out[15]: c 3 a 3 d 1 b 2 Name: count, dtype: int64 In [16]: pd.value_counts(obj.to_numpy(), sort=True) Out[16]: c 3 a 3 b 2 d 1 Name: count, dtype: int64
isin 메서드는 어떤 값이 Series에 존재하는지 나타내는 불리언 벡터 반환
Series나 DataFrame의 열에서 값을 골라내고 싶을 때 사용
In [17]: obj # obj는 Pandas로 만들어진 Series 객체 Out[17]: 0 c 1 a 2 d 3 a 4 a 5 b 6 b 7 c 8 c dtype: object In [18]: mask = obj.isin(['b', 'c']) In [19]: mask Out[19]: 0 True 1 False 2 False 3 False 4 False 5 True 6 True 7 True 8 True dtype: bool In [20]: obj[mask] Out[20]: 0 c 5 b 6 b 7 c 8 c dtype: object
isin과 관련 있는 Index_get.indexer 메서드는 여러 값이 들어 있는 배열에서 유일한 값의 인덱스 배열을 구할 수 있음
In [23]: to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a']) In [24]: unique_vals = pd.Series(['c', 'b', 'a']) In [25]: indices = pd.Index(unique_vals).get_indexer(to_match) In [26]: indices Out[26]: array([0, 2, 1, 1, 0, 2], dtype=int64)
메서드 설명 isin Series의 각 원소가 넘겨받은 연속된 값에 속하는지 나타내는 불리언 배열 반환 get_indexer 각 값에 대해 유일한 값을 담고 있는 배열에서의 정수 인덱스 계산, 데이터 정렬이나 조인 형태의 연산을 하는 경우 unique Series에서 중복되는 값을 제거하고 유일한 값만 포함하는 배열 반환, 결과는 Series에서 발견된 순서대로 반환 value_counts Series에서 유일한 값에 대한 인덱스와 도수 계산, 도수는 내림차순으로 정렬됨 DataFrame의 여러 열에 대해 히스토그램(histogram) 계산
In [27]: data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4], ...: 'Qu2': [2, 3, 1, 2, 3], ...: 'Qu3': [1, 5, 2, 4, 4]}) In [28]: data Out[28]: Qu1 Qu2 Qu3 0 1 2 1 1 3 3 5 2 4 1 2 3 3 2 4 4 4 3 4 In [29]: data['Qu1'].value_counts() # 단일 열 계산 Out[29]: Qu1 3 2 4 2 1 1 Name: count, dtype: int64 In [30]: data['Qu1'].value_counts().sort_index() Out[30]: Qu1 1 1 3 2 4 2 Name: count, dtype: int64
모든 열에서 위와 같은 계산 하려면 DataFrame의 apply 메서드에 pandas.value_counts를 넘기면 됨
In [31]: result = data.apply(pd.value_counts).fillna(0) # 위의 계산 모든 열 적용 In [32]: result Out[32]: Qu1 Qu2 Qu3 1 1.0 1.0 1.0 2 0.0 2.0 1.0 3 2.0 2.0 0.0 4 2.0 0.0 2.0 5 0.0 0.0 1.0 # + 계산할 때 NaN이 생기면 에러 생길 수 있으므로 0으로 채워 주기 # fillna(0) 유무 비교 In [35]: data.apply(pd.value_counts) Out[35]: Qu1 Qu2 Qu3 1 1.0 1.0 1.0 2 NaN 2.0 1.0 3 2.0 2.0 NaN 4 2.0 NaN 2.0 5 NaN NaN 1.0 In [36]: data.apply(pd.value_counts).fillna(0) Out[36]: Qu1 Qu2 Qu3 1 1.0 1.0 1.0 2 0.0 2.0 1.0 3 2.0 2.0 0.0 4 2.0 0.0 2.0 5 0.0 0.0 1.0
전체 열의 유일한 값이 결괏값의 행 레이블, 각 값은 빈도를 나타냄
DataFrame.value_counts 메서드도 있지만 DataFrame의 각 행을 튜플로 간주해 구별되는 행의 수 계산
In [37]: data = pd.DataFrame({'a': [1, 1, 1, 2, 2], 'b': [0, 0, 1, 0, 0]}) In [38]: data Out[38]: a b 0 1 0 1 1 0 2 1 1 3 2 0 4 2 0 In [39]: data.value_counts() Out[39]: a b 1 0 2 2 0 2 1 1 1 Name: count, dtype: int64
계층적 인덱스로 고유한 행 표시
'Python' 카테고리의 다른 글
데이터 정제 및 준비 (0) 2023.11.21 데이터 로딩과 저장, 파일 형식 (0) 2023.11.20 넘파이 기본: 배열과 벡터 연산 (0) 2023.11.07 파이썬 기초와 Ipython, jupyter notebook (0) 2023.10.31 웹의 데이터로 그래프 그리기 (0) 2023.10.31