Tabular 데이터핸들링 라이브러리인 Pandas의 2버전이 출시되었다. 이와 관련해여 여러 변경 사항을 알아보자.
개요
드디어 Pandas 2 버전이 출시되었다. 1 버전이 2020년 1월 19일 출시된 이후 약 3년만이다. Pandas 깃헙 저장소에는 엄청난 이슈와 풀리퀘(pull requests)가 등록되어있는데 사람들이 활발하게 기여하는 모습을 보면 오히려 늦은 것 같기도 하다.
아무튼 Pandas 공식 사이트에서 제공하는 2버전에서 새로워진 부분을 언급한 웹페이지를 참고하여 작성하였으니 원문을 확인하고자 하는 사람은 해당 페이지를 방문해보면 되겠다.
설치
현재 포스팅 시점으로 최신 버전은 “2.0.0rc1” 이다. 그래서 다음과 같이 주피터 노트북에서 “!pip” 명령어로 설치하면 잘 설치가 되는 것을 볼 수 있다.
※ 아래 실행 결과는 Pandas 2.0.0rc0 버전이 설치 되어있던 상황에서 2.0.0rc1 버전을 설치한 결과이다.
1 | !pip install pandas==2.0.0rc1 |
변경점
이전 Pandas 버전(1.5.3) 대비 변경점은 다음과 같다.
개선(Enhancements)
이제 Pandas를 설치할 때 extras를 지정하여 종속된 라이브러리를 설치할 수 있다고 한다. 그런데 공식 페이지에 다음과 같이 사용할 수 있다고 안내하지만 실행해보면 에러가 난다. 주피터노트북 환경에서 “!pip” 로 설치해도 제대로 실행되지 않는다.
1 | pip install "pandas[performance, aws]>=2.0.0" |
Anaconda 프롬프트에서 다음과 같이 설치 시도를 했으나 버전 인식을 못하는지 설치가 제대로 되지 않았다.
아직은 명령어 지원이 수월하게 되지 않는 것으로 보인다.
성능 향상(Performance improvements)
데이터 유형별, 멀티인덱스, 파일 읽어오기 등 다양한 부분에서 성능 향상이 이루어졌으며 주요 내용은 다음과 같다.
nullable 데이터 유형에 대한
DataFrameGroupBy.median()
,SeriesGroupBy.median()
,DataFrameGroupBy.cumprod()
의 성능 향상nullable 데이터 유형에 대한
Series.value_counts()
의 성능 향상nullable 데이터 유형에 대해
.var()
,.std()
의 성능 향상nullable 데이터 유형에 대해
Series.median()
의 성능 향상object 데이터 유형에 대한
DataFrameGroupBy.all()
,DataFrameGroupBy.any()
,SeriesGroupBy.all()
,SeriesGroupBy.any()
의 성능 향상NumPy Array의 확장 데이터 유형에 대한
DataFrameGroupBy.mean()
,SeriesGroupBy.mean()
,DataFrameGroupBy.var()
,SeriesGroupBy.var()
의 성능 향상NumPy Array의 확장 데이터 유형에 대한
Series.fillna()
의 성능 향상정렬된 멀티인덱스 객체의 join 연산에서의
merge()
와DataFrame.join()
의 성능 향상시간지역 오프셋(timezone offset)을 포함한 문자열도 이제
to_datetime()
함수가 지원- 이 부분은 사실상 별도로 timedelta로 처리하기 때문에 큰 장점이 있는지 불명확하다.
1
2
3
4
5
6
7# 1.5.3
pd.to_datetime("2023-03-23 12:34:56+9:00")
## Timestamp('2023-03-23 12:34:56+0900', tz='pytz.FixedOffset(540)')
# 2.0.0rc1
pd.to_datetime("2023-03-23 12:34:56+9:00")
## Timestamp('2023-03-23 12:34:56+0900', tz='UTC+09:00')
- 이 부분은 사실상 별도로 timedelta로 처리하기 때문에 큰 장점이 있는지 불명확하다.
DataFrame.loc()
와Series.loc()
에 튜플 기반의 멀티인덱스의 인덱싱 가능멀티인덱스에서 특정 레벨의 인덱스의 복수 인덱스 지정이 1.5.1 버전에선 오류가 났었는데 우선 다음과 같이 데이터를 준비한다.
1
2
3df = pd.DataFrame([[1, 2], [3, 4], [5, 6], [7, 8]],
index = [["A", "A", "B", "B"], ["C", "D", "C", "D"]])
df0 1 A C 1 2 D 3 4 B C 5 6 D 7 8 업데이트로 인해 새로 동작하는 코드는 다음과 같다.
1
2
3
4
5
6# 기존 코드도 올바르게 동작
df.loc[("A", "D"), ]
df.loc[(["A"], ["D"]), ]
# 1.5.3에서 에러가 발생하던 코드
df.loc[(["A"], ["C", "D"]), ]0 1 A C 1 2 D 3 4
멀티인덱스 일부를 join할 때
DataFrame.join()
의 성능 향상멀티인덱스에서
Series.rename()
의 성능 향상범주형 데이터(categorical type)에 대해
Series.replace()
의 성능 향상범주형 데이터(categorical type)에
DataFrame.GroupBy()
,Series.GroupBy()
사용시 “sort=False”와 “observed=False” 설정에서의 성능 향상read_sas()
의 성능 향상read_stata()
의 “index_col” 파라미터의 기본값이None
으로 지정되었으며 이제 인덱스는 “RangeIndex”가 아닌 “Int64Index”로 지정됨복수 테이블에
read_html()
를 사용하는 경우 성능 향상“%Y%m%d” 유형에
to_datetime()
를 사용하는 경우 성능 향상유추 가능한 형식의 데이터를
to_datetime()
로 처리하는 경우 성능 향상DataFrame.to_pickle()
,Series.to_pickle()
으로 “BZ2” 또는 “LZMA” 방식을 사용할 때 메모리 사용량 감소to_numpy()
의 성능 향상object가 아닌 데이터 유형에서
DataFrame.to_dict()
와Series.to_dict()
의 성능 향상date_parser와 시간대 오프셋이 섞여있는 데이터에 lambda 함수가 있는
to_datetime()
를read_csv()
에 넘기는 경우 성능 향상isna()
와isnull()
의 성능 향상범주형 데이터(categorical type)에 사용하는
SeriesGroupBy.value_counts()
의 성능 향상DataFrame.to_json()
또는Series.to_json()
로 datetime와 timedelta 데이터 유형을 직렬화(serializing)하는 경우 발생하는 메모리 누수 보완여러
DataFrame.GroupBy()
메서드의 메모리 사용량 감소DataFrame.round()
의 자리수 지정 파라미터 관련 성능 향상DataFrame.replace()
또는Series.replace()
에 큰 딕셔너리 객체를 사용하는 경우 성능 향상
API 호환 변경(Backwards incompatible API changes)
datetime64와 timedelta64 데이터 유형의 다양한 시간 해상도(resolution) 지원
- 나노초 단위로 지정되었으나 이제 “s”, “ms”, “us”, “ns” 중 하나 선택 가능
1
2
3
4
5
6
7
8
9
10
11
12
13# 이전 버전
pd.Series(["2016-01-01"], dtype="datetime64[s]")
## 0 2016-01-01
## dtype: datetime64[ns]
pd.Series(["2016-01-01"], dtype="datetime64[D]")
## 0 2016-01-01
## dtype: datetime64[ns]
# 신규 버전
pd.Series(["2016-01-01"], dtype="datetime64[s]")
## 0 2016-01-01
## dtype: datetime64[s]
- 나노초 단위로 지정되었으나 이제 “s”, “ms”, “us”, “ns” 중 하나 선택 가능
다양한 시간 해상도를 지원하도록 개선되어 .astype() 메서드 또한 이를 지원
※ “s”, “ms”, “us”, “ns”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 이전 버전
idx = pd.date_range("2016-01-01", periods=3)
ser = pd.Series(idx)
ser.astype("datetime64[s]")
## 0 2016-01-01
## 1 2016-01-02
## 2 2016-01-03
## dtype: datetime64[ns]
# 신규 버전
idx = pd.date_range("2016-01-01", periods=3)
ser = pd.Series(idx)
ser.astype("datetime64[s]")
## 0 2016-01-01
## 1 2016-01-02
## 2 2016-01-03
## dtype: datetime64[s]이제 .astype() 으로 일별 증분(timedelta64[D])을 지정할 수 없음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 이전 버전
idx = pd.timedelta_range("1 Day", periods=3)
ser = pd.Series(idx)
ser.astype("timedelta64[s]")
## 0 86400.0
## 1 172800.0
## 2 259200.0
## dtype: float64
ser.astype("timedelta64[D]")
## 0 1.0
## 1 2.0
## 2 3.0
## dtype: float64
# 신규 버전
idx = pd.timedelta_range("1 Day", periods=3)
ser = pd.Series(idx)
ser.astype("timedelta64[s]")
## 0 86400.0
## 1 172800.0
## 2 259200.0
## dtype: float64
ser.astype("timedelta64[D]")
## 에러!!!.value_counts() 메서드에 이제 이름이 올바로 지정됨
1
2
3
4
5
6
7
8
9
10
11
12# 이전 버전
pd.Series(['quetzal', 'quetzal', 'elk'], name='animal').value_counts()
## quetzal 2
## elk 1
## Name: count, dtype: int64
# 신규 버전
pd.Series(['quetzal', 'quetzal', 'elk'], name='animal').value_counts()
## animal
## quetzal 2
## elk 1
## Name: count, dtype: int64비어있는 DataFrame 또는 Series 객체의 인덱스는 RangeIndex로 지정됨
1
2
3
4
5
6
7
8
9
10
11
12
13# 이전 버전
pd.Series().index
## Index([], dtype='object')
pd.DataFrame().axes
## [Index([], dtype='object'), Index([], dtype='object')]
# 신규 버전
pd.Series().index
## RangeIndex(start=0, stop=0, step=1)
pd.DataFrame().axes
## [RangeIndex(start=0, stop=0, step=1), RangeIndex(start=0, stop=0, step=1)]이제 날짜 형식이 일관된 형식으로 변환됨
- 예전엔 각 원소를 적당히 유추해서 했으나 이제는 전체 원소를 일관된 형식으로 변환
to_datetime()
의 “dayfirst” 인자에True
를 할당하여 날짜로 먼저 시작하는지 지정 가능1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 이전 버전
ser = pd.Series(['13-01-2000', '12-01-2000'])
pd.to_datetime(ser)
## 0 2000-01-13
## 1 2000-12-01
## dtype: datetime64[ns]
# 신규 버전
ser = pd.Series(['13-01-2000', '12-01-2000'])
pd.to_datetime(ser) # 경고 발생
## 0 2000-01-13
## 1 2000-01-12
## dtype: datetime64[ns]
ser = pd.Series(['13-01-2000', '12-01-2000'])
pd.to_datetime(ser, dayfirst = True)
## 0 2000-01-13
## 1 2000-01-12
## dtype: datetime64[ns]
주요 버그 수정(Notable bug fixes)
DataFrameGroupBy.cumsum()
와DataFrameGroupBy.cumprod()
연산시 float으로 변환되는 대신 overflow 발생1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 이전 버전
df = pd.DataFrame({"key": ["b"] * 7, "value": 625})
df.groupby("key")["value"].cumprod()[5]
## 5.960464477539062e+16
# 신규 버전
df = pd.DataFrame({"key": ["b"] * 7, "value": 625})
df.groupby("key")["value"].cumprod()
## 0 625
## 1 390625
## 2 244140625
## 3 152587890625
## 4 95367431640625
## 5 59604644775390625
## 6 359414837200037393
## Name: value, dtype: int64datetime64이나 timedelta64 데이터 유형에서 해상도 지원부분 수정
지원 중단(Deprecations)
주로 데이터 타입과 관련된 지원 중단 소식이 많다. 기존 코드가 조금씩 더 길어질 것 같지만 코드를 일원화 하고 통일하려고 하는 움직임이 느껴진다.
Index.is_boolean()
X →pandas.api.types.is_bool_dtype()
Index.is_integer()
X →pandas.api.types.is_integer_dtype()
Index.is_floating()
X →pandas.api.types.is_float_dtype()
Index.holds_integer()
X →pandas.api.types.infer_dtype()
Index.is_numeric()
X →pandas.api.types.is_any_real_numeric_dtype()
Index.is_categorical()
X →pandas.api.types.is_categorical_dtype()
Index.is_object()
X →pandas.api.types.is_object_dtype()
Index.is_interval()
X →pandas.api.types.is_interval_dtype()
참고
NumPy - Extension array dtypes
NumPy Array의 확장 데이터 유형(Extension array dtypes)는 NumPy의 데이터 타입 시스템을 확장한 사용자 정의 데이터 타입(문자열, 날짜, 시간, 지리 정보 등)
Pandas - nullable dtype
Pandas에서 nullable data type은 “Nullable”이라고도 불리며, “nullable dtype”라는 용어로도 흔히 사용된다. 이는 Pandas 1.0 버전 이후 도입된 개념으로, 일반적인 데이터 유형의 확장으로 볼 수 있다.
기존의 Pandas 데이터 유형의 경우, 예를 들어 정수형 데이터 유형인 int64는 결측치(missing value)를 표현할 수 있는 값이 없기 때문에, 결측치가 있는 데이터를 다루기에는 제한이 있었다. 하지만 nullable dtype을 사용하면 결측치를 표현할 수 있다.
Pandas에서 nullable dtype으로 사용 가능한 데이터 유형에는 다음과 같은 것들이 있습니다.
- 정수형 데이터 유형: Int8, Int16, Int32, Int64
- 부호가 없는 정수형 데이터 유형: UInt8, UInt16, UInt32, UInt64
- 부동소수점 데이터 유형: Float32, Float64
- 불리언 데이터 유형: Boolean
nullable dtype을 사용하면, 해당 데이터 유형으로 생성한 Series나 DataFrame에서 결측치를 표현할 수 있는 특별한 값을 사용할 수 있습니다. 해당 값은 보통 NaN(Not a Number)으로 표시된다.