판다스 라이브러리는 티블(tibbles),데이터프레임(dataframe),시리즈(series) 등 데이터 처리를 위한 모듈.

import pandas as pd  # 판다스 로딩 및 버젼 확인
print("=== pandas version: {} ===\n".format(pd.__version__))
import sys
print("=== Python version ===\n{}".format(sys.version))
=== pandas version: 1.4.3 ===

=== Python version ===
3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]

판다스는 2D 테이블 형식의 DataFrame, 데이터프레임의 한개 컬럼 형식의 Series를 주로 다룬다.

from pandas import DataFrame, Series

Series

티블의 변수를 저장할 때 쓰는 컬럼 형식.

obj = Series([1, -2, 3, -4, 5])  
obj
# 결과: 인덱스와 dtype: int64의 값 확인
0    1
1   -2
2    3
3   -4
4    5
dtype: int64

Series는 파이썬의 list보다 처리 속도가 빠르다.

string과 int, float이 섞이면 Seriesdtypeobject

obj2 = Series(['1', -2, '3', -4, 5])
obj2
0     1
1    -2
2     3
3    -4
4     5
dtype: object
print(obj.dtype)
print(obj2.dtype)
int64
object

nSeries의 length일 때 Seriesrange(0, n), 즉 0 ~ n-1까지의 인덱스를 가진다.

list(obj.index)
[0, 1, 2, 3, 4]
obj[1:4]
1   -2
2    3
3   -4
dtype: int64
I = [0, 2, 3] # 인덱스 설정
obj[I] # obj[[0, 2, 3]]와 같음
0    1
2    3
3   -4
dtype: int64
I_index = obj < 0  # obj 값이 0보다 작은 인덱스이다. (조건)
I_index # bool 타입 보여줌
0    False
1     True
2    False
3     True
4    False
dtype: bool
obj[I_index]
1   -2
3   -4
dtype: int64

Series를 딕셔너리처럼 만들기

obj3 = Series([1, -2, 3, -4, 5, -6],
              ['a', 'b', 'c', 'd', 'e', 'f'])
obj3
a    1
b   -2
c    3
d   -4
e    5
f   -6
dtype: int64
dic = {'a': 1, 'c': 3, 'e': 5, 'b': -2, 'd': -4, 'f': -6}   #딕셔너리를 시리즈로 만들기
obj4 = Series(dic)
print(obj4)
a    1
c    3
e    5
b   -2
d   -4
f   -6
dtype: int64
index = [0, 2, 4] # 인덱스 리스트
obj4[index]
a    1
e    5
d   -4
dtype: int64
names = ['c', 'e', 'f'] # 인덱스 값 리스트
obj4[names]
c    3
e    5
f   -6
dtype: int64
I_index = obj4 < 0  # 조건 : 값<0
I_index
a    False
c    False
e    False
b     True
d     True
f     True
dtype: bool
obj4[I_index]  # 조건 성립하는 값들
b   -2
d   -4
f   -6
dtype: int64

isin : 조건, 검색

딕셔너리처럼 Seriesin 사용가능. 하지만 대체로 listSeries에서 in보다는 Series.isin 사용한다.

'c' in obj4
True

Series도 벡터처럼 연산가능하다.

print(obj3, "\n")
print(obj3 + 5, "\n")
print(obj3 + 5 > 0, "\n")
print((-2.5 * obj3) + (obj3 + 5))
a    1
b   -2
c    3
d   -4
e    5
f   -6
dtype: int64 

a     6
b     3
c     8
d     1
e    10
f    -1
dtype: int64 

a     True
b     True
c     True
d     True
e     True
f    False
dtype: bool 

a     3.5
b     8.0
c     0.5
d    11.0
e    -2.5
f    14.0
dtype: float64

Series 객체도 인덱스를 기준으로 vector 스타일로 동작한다.

print(obj3)
a    1
b   -2
c    3
d   -4
e    5
f   -6
dtype: int64
obj_l = obj3[[0,2,4]]
obj_l
a    1
c    3
e    5
dtype: int64
obj3 + obj_l
a     2.0
b     NaN
c     6.0
d     NaN
e    10.0
f     NaN
dtype: float64

지정하지않은 요소는 NaN값으로 출력된다. (outer-join 과 유사)

.apply(fun)

각 요소에 함수를 적용한 Series의 copy가 출력된다.

abs(-7) # Python built-in function
7
obj3.apply(abs) # 모든 요소를 절대값으로 바꾸어 복사본 출력
a    1
b    2
c    3
d    4
e    5
f    6
dtype: int64
obj3 # apply 적용 후 복사본이 출력되고 원본데이터는 변하지 않는다.
a    1
b   -2
c    3
d   -4
e    5
f   -6
dtype: int64

Series 네이밍하기

print(obj3.name)
None
obj3.name = 'test'
obj3
a    1
b   -2
c    3
d   -4
e    5
f   -6
Name: test, dtype: int64

DataFrame

동일 인덱스를 가진 Series 컬럼들을 가진다.

member = DataFrame({'nick': ['jr', 'ky', 'mt', 'jt', 'nh', 'de', 'kt', 'nn'],
                   'id': [30324, 30312, 30316, 30317, 30306, 30308, 30303, 30318],
                   'name': ['jared', 'kelly', 'matt', 'jonathan', 'nhan', 'dale', 'kitti', 'nolan']})
print(type(member), member)
<class 'pandas.core.frame.DataFrame'>   nick     id      name
0   jr  30324     jared
1   ky  30312     kelly
2   mt  30316      matt
3   jt  30317  jonathan
4   nh  30306      nhan
5   de  30308      dale
6   kt  30303     kitti
7   nn  30318     nolan
display(member) # member만 입력해도 동일
nick id name
0 jr 30324 jared
1 ky 30312 kelly
2 mt 30316 matt
3 jt 30317 jonathan
4 nh 30306 nhan
5 de 30308 dale
6 kt 30303 kitti
7 nn 30318 nolan
member.columns  # 컬럼 확인 
Index(['nick', 'id', 'name'], dtype='object')
type(member['name']) # 각 컬럼은 Series
pandas.core.series.Series
member.index # 인덱스 확인
RangeIndex(start=0, stop=8, step=1)
member.index == member['nick'].index # 모두 동일 인덱스 가짐
array([ True,  True,  True,  True,  True,  True,  True,  True])
target_fields = ['name', 'id']  # 컬럼 지정
member[target_fields]
name id
0 jared 30324
1 kelly 30312
2 matt 30316
3 jonathan 30317
4 nhan 30306
5 dale 30308
6 kitti 30303
7 nolan 30318

슬라이싱

기본적으로 행 단위로 적용된다.

member[1::2] # 1부터 2칸씩 띄워서 출력
nick id name
1 ky 30312 kelly
3 jt 30317 jonathan
5 de 30308 dale
7 nn 30318 nolan
member2 = member[['name', 'nick']]
member2.index = member['id']  # 인덱스는 id 컬럼으로
member2.index.name = None
member2
name nick
30324 jared jr
30312 kelly ky
30316 matt mt
30317 jonathan jt
30306 nhan nh
30308 dale de
30303 kitti kt
30318 nolan nn

.loc

인덱스의 값으로 해당 행 찾기

member2.loc[[30312, 30308]]
name nick
30312 kelly ky
30308 dale de

.iloc

0-based 값인 디폴트 인덱스로 해당 행 찾기

member2.iloc[[1, 3]]
name nick
30312 kelly ky
30317 jonathan jt

컬럼 추가하기

member2['rating'] = 4.
member2['income'] = '$$'
member2
name nick rating income
30324 jared jr 4.0 $$
30312 kelly ky 4.0 $$
30316 matt mt 4.0 $$
30317 jonathan jt 4.0 $$
30306 nhan nh 4.0 $$
30308 dale de 4.0 $$
30303 kitti kt 4.0 $$
30318 nolan nn 4.0 $$
income_as_ints = member2['income'].apply(lambda x: len(x))  # $$의 길이를 integer로 표현
income_as_ints
30324    2
30312    2
30316    2
30317    2
30306    2
30308    2
30303    2
30318    2
Name: income, dtype: int64
member2['value'] = member2['rating'] / income_as_ints  # rating을 income으로 나눠 member들의 가치를 나타냄
member2
name nick rating income value
30324 jared jr 4.0 $$ 2.0
30312 kelly ky 4.0 $$ 2.0
30316 matt mt 4.0 $$ 2.0
30317 jonathan jt 4.0 $$ 2.0
30306 nhan nh 4.0 $$ 2.0
30308 dale de 4.0 $$ 2.0
30303 kitti kt 4.0 $$ 2.0
30318 nolan nn 4.0 $$ 2.0

모든 컬럼이 동일 인덱스를 가진 Series이므로 value컬럼 생성이 가능했다.

matt와 jonathan의 income 증가로 '$'가 하나 더 늘었다고 가정하고 기존 dataframe을 copy하여 member3로 저장한다.

member3 = member2.copy()
hike = member3['name'].isin({'matt', 'jonathan'})
# 또는 hike = member3['name'].apply(lambda x: x in {'matt', 'jonathan'})
hike
30324    False
30312    False
30316     True
30317     True
30306    False
30308    False
30303    False
30318    False
Name: name, dtype: bool
member3[hike]
name nick rating income value
30316 matt mt 4.0 $$ 2.0
30317 jonathan jt 4.0 $$ 2.0
# string 연결해서 $ 추가
s = '$$'
s += '$'
print(s)
$$$
# member3[hike]['income'] += '$' 하면 다음의 에러 메시지가 난다. 
# A value is trying to be set on a copy of a slice from a DataFrame.
# Try using .loc[row_indexer,col_indexer] = value instead
member3
name nick rating income value
30324 jared jr 4.0 $$ 2.0
30312 kelly ky 4.0 $$ 2.0
30316 matt mt 4.0 $$ 2.0
30317 jonathan jt 4.0 $$ 2.0
30306 nhan nh 4.0 $$ 2.0
30308 dale de 4.0 $$ 2.0
30303 kitti kt 4.0 $$ 2.0
30318 nolan nn 4.0 $$ 2.0

가로로 슬라이싱하면 데이터 원본이 아닌 복사본에 적용되어 원본 데이터의 하위 집합에 대한 참조가 없다.

# .loc[row_indexer,col_indexer] = value
member3.loc[hike, 'income'] += '$'
member3
name nick rating income value
30324 jared jr 4.0 $$ 2.0
30312 kelly ky 4.0 $$ 2.0
30316 matt mt 4.0 $$$ 2.0
30317 jonathan jt 4.0 $$$ 2.0
30306 nhan nh 4.0 $$ 2.0
30308 dale de 4.0 $$ 2.0
30303 kitti kt 4.0 $$ 2.0
30318 nolan nn 4.0 $$ 2.0
member4 = member2.copy() 

income이 증가한 해당 인덱스를 Series로 저장하고 string 연결을 하는 방법도 있다. 그러나 지정되지 않은 값들은 NaN으로 표시된다.

hike_id = member4.index[hike]
hike_mark = Series(['$'] * len(hike_id), index=hike_id)
print(hike_id)
print(hike_mark)
Int64Index([30316, 30317], dtype='int64')
30316    $
30317    $
dtype: object
member4['income'] + hike_mark
30303    NaN
30306    NaN
30308    NaN
30312    NaN
30316    $$$
30317    $$$
30318    NaN
30324    NaN
dtype: object

income 증가여부를 bool 타입 list로 만들어 ‘$’를 곱하고 string 연결한다.

hike.tolist()   # True, False로 표시됨
[False, False, True, True, False, False, False, False]
[x * '$' for x in list(hike.tolist())]   # 각각의 요소에 '$'를 곱해줌
['', '', '$', '$', '', '', '', '']
member4 = member2.copy()
member4['income'] += Series([x * '$' for x in hike.tolist()], index=hike.index)
member4
name nick rating income value
30324 jared jr 4.0 $$ 2.0
30312 kelly ky 4.0 $$ 2.0
30316 matt mt 4.0 $$$ 2.0
30317 jonathan jt 4.0 $$$ 2.0
30306 nhan nh 4.0 $$ 2.0
30308 dale de 4.0 $$ 2.0
30303 kitti kt 4.0 $$ 2.0
30318 nolan nn 4.0 $$ 2.0

DataFrame에서는 대체로 apply() 함수를 사용한다.

help(repr)  # 해당 객체의 원래값을 재현할 수 있도록한 개발자를 위한 함수
Help on built-in function repr in module builtins:

repr(obj, /)
    Return the canonical string representation of the object.
    
    For many object types, including most builtins, eval(repr(obj)) == obj.
member4.apply(lambda x: repr(type(x)))  # 행 방향
name      <class 'pandas.core.series.Series'>
nick      <class 'pandas.core.series.Series'>
rating    <class 'pandas.core.series.Series'>
income    <class 'pandas.core.series.Series'>
value     <class 'pandas.core.series.Series'>
dtype: object
member4.apply(lambda x: repr(type(x)), axis=1) # 열 방향
30324    <class 'pandas.core.series.Series'>
30312    <class 'pandas.core.series.Series'>
30316    <class 'pandas.core.series.Series'>
30317    <class 'pandas.core.series.Series'>
30306    <class 'pandas.core.series.Series'>
30308    <class 'pandas.core.series.Series'>
30303    <class 'pandas.core.series.Series'>
30318    <class 'pandas.core.series.Series'>
dtype: object

apply()를 이용한 값 수정

‘value’값을 수정해보자.

def update_value(row):
    return row['rating'] / len(row['income'])

member4['value'] = member4.apply(update_value, axis=1) # 열 방향 동작
member4
name nick rating income value
30324 jared jr 4.0 $$ 2.000000
30312 kelly ky 4.0 $$ 2.000000
30316 matt mt 4.0 $$$ 1.333333
30317 jonathan jt 4.0 $$$ 1.333333
30306 nhan nh 4.0 $$ 2.000000
30308 dale de 4.0 $$ 2.000000
30303 kitti kt 4.0 $$ 2.000000
30318 nolan nn 4.0 $$ 2.000000

.concat()

데이터프레임 결합하기

# income에 따라 분할하기
low = member4['income'] <= '$$'  # '$$'보다 작은 income을 low라고 지정
member_low = member4[low]
member_high = member4[~low]      # ~ low: 조건이 low가 아닌 것

display(member_high)
display(member_low)
name nick rating income value
30316 matt mt 4.0 $$$ 1.333333
30317 jonathan jt 4.0 $$$ 1.333333
name nick rating income value
30324 jared jr 4.0 $$ 2.0
30312 kelly ky 4.0 $$ 2.0
30306 nhan nh 4.0 $$ 2.0
30308 dale de 4.0 $$ 2.0
30303 kitti kt 4.0 $$ 2.0
30318 nolan nn 4.0 $$ 2.0
# 결합
pd.concat([member_high, member_low])
name nick rating income value
30316 matt mt 4.0 $$$ 1.333333
30317 jonathan jt 4.0 $$$ 1.333333
30324 jared jr 4.0 $$ 2.000000
30312 kelly ky 4.0 $$ 2.000000
30306 nhan nh 4.0 $$ 2.000000
30308 dale de 4.0 $$ 2.000000
30303 kitti kt 4.0 $$ 2.000000
30318 nolan nn 4.0 $$ 2.000000

인덱스 객체

from pandas import Index
print(member4.index)
member4.index.isin([30324, 30317])   # 해당 인덱스인지 아닌지 bool로 출력
Int64Index([30324, 30312, 30316, 30317, 30306, 30308, 30303, 30318], dtype='int64')





array([ True, False, False,  True, False, False, False, False])

index.union()

인덱스 추가

member4.index.union([30000])  # 인덱스 추가
Int64Index([30000, 30303, 30306, 30308, 30312, 30316, 30317, 30318, 30324], dtype='int64')

index.difference()

인덱스 제외, 삭제

member4.index.difference([30312, 30318, 30303])  #인덱스 제외
Int64Index([30306, 30308, 30316, 30317, 30324], dtype='int64')

인덱스 변경

member5 = member4.reindex(Index([30308, 30313, 30000, 30312, 30317]))  # 새로운 인덱스 순으로 정렬 (없는인덱스는 NaN)

display(member4)
display(member5)
name nick rating income value
30324 jared jr 4.0 $$ 2.000000
30312 kelly ky 4.0 $$ 2.000000
30316 matt mt 4.0 $$$ 1.333333
30317 jonathan jt 4.0 $$$ 1.333333
30306 nhan nh 4.0 $$ 2.000000
30308 dale de 4.0 $$ 2.000000
30303 kitti kt 4.0 $$ 2.000000
30318 nolan nn 4.0 $$ 2.000000
name nick rating income value
30308 dale de 4.0 $$ 2.000000
30313 NaN NaN NaN NaN NaN
30000 NaN NaN NaN NaN NaN
30312 kelly ky 4.0 $$ 2.000000
30317 jonathan jt 4.0 $$$ 1.333333

인덱스 리셋

디폴트 인덱스값으로 되돌리기

member6 = member4.reset_index(drop=True)    # 원본데이터의 인덱스 drop
member6['id'] = member4.index      # 원본데이터의 인덱스는 'id'컬럼으로
member6
name nick rating income value id
0 jared jr 4.0 $$ 2.000000 30324
1 kelly ky 4.0 $$ 2.000000 30312
2 matt mt 4.0 $$$ 1.333333 30316
3 jonathan jt 4.0 $$$ 1.333333 30317
4 nhan nh 4.0 $$ 2.000000 30306
5 dale de 4.0 $$ 2.000000 30308
6 kitti kt 4.0 $$ 2.000000 30303
7 nolan nn 4.0 $$ 2.000000 30318

참고: Python for Data Analysis (2nd ed.)