Python 공부 기록-8

김시헌·2025년 4월 2일

시리즈 수정/추가/삭제

시리즈 객체의 값을 수정, 추가, 삭제해보자. 시리즈의 수정은 loc 혹은 iloc로 특정 위치에 접근한 뒤 변경할 값을 넣어주면 된다.

다음 코드는 메로나 인덱스에 저장된 1000이라는 값을 500으로 변경한다. 킷값을 사용하는 딕셔너리와 유사한 형태이다.

코드

from pandas import Series

data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)

s.loc['메로나'] = 500
print(s)

실행 결과
메로나 500
구구콘 2000
하겐다즈 3000
dtype: int64

앞서 배운 iloc 연산을 사용하거나 [ ] 기호를 사용해서 메로나의 가격을 수정할 수 있다. 메로나 인덱스가 존재하기 때문에 기존의 값이 새로운 값으로 업데이트된다.

코드

s.iloc[0] = 500
s['메로나'] = 500 

시리즈에 인덱스가 존재하지 않으면 새로운 값이 추가된다. 다음 코드는 비비빅이라는 인덱스에 값 500을 추가한다. 시리즈의 인덱스에 해당하는 값이 있으면 수정되고 없으면 추가된다.

코드

s.iloc[0] = 500
s['메로나'] = 500 

s.loc['비비빅'] = 500
print(s)

실행 결과
메로나 500
구구콘 2000
하겐다즈 3000
비비빅 500
dtype: int64

시리즈에서 값의 삭제는 drop 메서드를 사용한다. drop 메서드의 인자로 인덱스를 넣어주면 해당 인덱스의 값이 삭제된다. 참고로 drop 메서드는 시리즈의 인덱스를 사용해야 한다. 라인 2의 코드처럼 행 번호를 넘겨주면 drop 메서드에서 에러를 발생한다.

코드

print(s.drop('메로나'))
print(s.drop(0))  #에러

실행 결과
구구콘 2000
하겐다즈 3000
비비빅 500
dtype: int64
에러문 출력

drop 메서드는 시리즈의 원본 데이터를 제거하지 않고 새로운 시리즈 객체를 반환하는 것에 유의해야 한다. 이는 실수로 원본 데이터를 수정해버리는 것을 방지하기 위한 일종의 안전장치이다. 다음과 같이 drop 메서드를 호출한 결과를 다시 변수에 바인딩하도록 코드를 작성해야 한다.

코드

s = s.drop('메로나')
print(s)

실행 결과
구구콘 2000
하겐다즈 3000
비비빅 500
dtype: int64

시리즈 연산

시리즈는 넘파이와 동일하게 브로드캐스팅이 적용되며, 같은 인덱스를 갖는 데이터끼리 연산을 수행한다.

간단한 예와 함께 시리즈의 덧셈에 대해서 알아보자. 철수가 NAVER 10주, SKT 30주, KT 30주를 보유하고 있고 영희가 SKT 10주, KT 20주, NAVER 20주를 보유하고 있을 때 철수와 영희 가족이 보유하고 있는 종목별 주식 수는 어떻게 될까? 이를 판다스 시리즈로 계산해 보자.

코드

from pandas import Series

철수 = Series([10, 20, 30], index=['NAVER', 'SKT', 'KT'])
영희 = Series([10, 30, 20], index=['SKT', 'KT', 'NAVER'])
가족 = 철수 + 영희
print(가족)

실행 결과
KT 60
NAVER 30
SKT 30
dtype: int64

라인 3: 철수의 주식 종목 보유 현황을 시리즈 객체로 표현한다.

라인 4: 영희의 주식 종목 보유 현황을 시리즈 객체로 표현힌다.

라인 5: 두 시리즈 객체를 더한다.

시리즈와 스칼라(숫자)의 연산은 브로드캐스팅이 적용되어 모든 데이터에 연산이 적용되며, 그 결과가 시리즈로 반환된다. 시리즈 객체로 데이터를 표현한 경우 for 문을 사용할 필요 없이 모든 데이터에 대해서 같은 연산을 쉽게 적용할 수 있다. 리스트보다 사용하기 쉽고 직관적이다

코드

print(철수 * 10)

실행 결과
NAVER 100
SKT 200
KT 300
dtype: int64

이번에는 삼성전자의 5일 일봉 데이터에서 고가와 저가 시리즈로 각 거래일의 변동폭을 계산해 보자. 여기서 변동폭이란 '고가와 저가의 차분값'이다.

코드

high = Series([42800, 42700, 42050, 42950, 43000])
low = Series([42150, 42150, 41300, 42150, 42350])

diff = high - low
print(diff)

실행 결과
0 650
1 550
2 750
3 800
4 650
dtype: int64

이번에는 앞서 계산한 diff 변수에서 변동폭이 가장 큰 값을 찾아보자. 넘파이를 확장해서 만든 것이 시리즈이므로, 넘파이에서 사용했던 함수와 메서드를 대부분 시리즈에서 사용할 수 있다. 넘파이에는 max 메서드가 있었기에 사용한다.

코드

print(diff.max())

실행 결과
800

min 사용 결과
550

날짜를 인덱스로 갖는 시리즈에서 변동폭이 가장 큰 날짜를 찾아보자.

코드

date = ["6/1", "6/2", "6/3", "6/4", "6/5"]
high = Series([42800, 42700, 42050, 42950, 43000], index=date)
low = Series([42150, 42150, 41300, 42150, 42350] , index=date)
diff = high - low
print(diff)

실행 결과
6/1 650
6/2 550
6/3 750
6/4 800
6/5 650
dtype: int64

시리즈는 자료구조이므로 반복문을 사용할 수 있다. 반복문으로 값에 하나씩 접근하면서, 비교를 통해 최댓값과 인덱스를 저장해 놓는다. 반복문이 끝나면 최댓값과 인덱스를 알 수 있으니 index를 하나 선택한다.

코드

max_idx = 0
max_val = 0

for i in range(len(diff)):
  if diff[i] > max_val:
    max_val = diff[i]
    max_idx = i

print(max_idx)
print(diff.index[max_idx])

실행 결과
3
6/4

<ipython-input-21-207228244103>:5: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
  if diff[i] > max_val:
<ipython-input-21-207228244103>:6: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
  max_val = diff[i]

시리즈는 최댓값일 때의 인덱스를 반환하는 idxmax와 최솟값일 때의 인덱스를 반환하는 idxmin 메서드를 제공한다. 반복문을 사용할 필요 없이 메서드를 호출하기만 하면 끝이다.

코드

print(diff.idxmax())
print(diff.idxmin())

실행 결과
6/4
6/2

이번에는 매일 저가에 사서 고가에 팔았을 경우 수익률을 계산해 보자. 현재 단계에서는 수수료는 고려하지 않고 단순 계산한다.

코드

date = ["6/1", "6/2", "6/3", "6/4", "6/5"]
high = Series([42800, 42700, 42050, 42950, 43000], index=date)
low = Series([42150, 42150, 41300, 42150, 42350] , index=date)
profit = high / low
print(profit)

실행 결과
6/1 1.015421
6/2 1.013049
6/3 1.018160
6/4 1.018980
6/5 1.015348
dtype: float64

누적 수익률은 모든 수익률을 누적해서 곱함으로써 계산할 수 있다. 반복문을 사용할 수도 있지만 판다스가 제공하는 cumprod 메서드를 사용하면 한 번에 결과를 얻을 수 있다.

코드

print( profit.cumprod( ) )

실행 결과
6/1 1.015421
6/2 1.028671
6/3 1.047351
6/4 1.067230
6/5 1.083610
dtype: float64

cumprod 메서드를 적용한 결과가 다시 시리즈이니 인덱싱으로 하나의 값을 가져올 수 있다.

코드

print( profit.cumprod( ).iloc[ -1 ] )

실행 결과
1.0836101509172456

시리즈 객체에 저장된 데이터 중 유일한 값을 뽑아내고 각 데이터의 개수도 카운트(count)하는 방법을 배워보자. 먼저 예제로 사용할 시리즈 객체를 생성한다. 딕셔너리 타입으로 데이터를 정의하고 시리즈 객체로 변환했다.

코드

data = {
    "삼성전자": "전기,전자",
    "LG전자": "전기,전자",
    "현대차": "운수장비",
    "NAVER": "서비스업",
    "카카오": "서비스업"
}
s = Series(data)

5개의 종목에 대한 업종 데이터를 시리즈 객체로 표현한 거다. 5개의 종목에서 중복을 제거하고 업종 리스트를 가져오고 싶을 때가 있다. 이 경우 unique 메서드를 사용한다.

코드

print(s.unique())

실행 결과
['전기,전자' '운수장비' '서비스업']

unique 메서드는 다음과 같이 시리즈의 values 중 유일한 값들을 ndarray로 반환합니다.

시리즈 안에 업종이 몇 번 존재하는지 횟수를 세어보자. value_counts 메서드를 사용하면 값의 출현 빈도를 계산해서 시리즈 객체로 반환한다.

코드

s.value_counts()

실행 결과

0개의 댓글