본문 바로가기
개발일지/Pandas

pandas 판다스 기초 11 체인인덱싱(chain indexing) 피하기, copy, 올바른 값 변경해주기

by 다니엘의 개발 이야기 2022. 7. 19.
320x100
# Manipulating Values in DataFrame
## Best Practise (how you should do it)

import pandas as pd

titanic = pd.read_csv('titanic.csv')
titanic.head()

'''
	survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
0	0	3	male	22.0	1	0	7.2500	S	NaN
1	1	1	female	38.0	1	0	71.2833	C	C
2	1	3	female	26.0	0	0	7.9250	S	NaN
3	1	1	female	35.0	1	0	53.1000	S	C
4	0	3	male	35.0	0	0	8.0500	S	NaN
'''
### Changing a single Value (Option 1 with loc)

# inplace = True 필요없음
titanic.loc[1, 'age'] = 40
titanic.head()

'''
    survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
0	0	3	male	22.0	1	0	7.2500	S	NaN
1	1	1	female	40.0	1	0	71.2833	C	C
2	1	3	female	26.0	0	0	7.9250	S	NaN
3	1	1	female	35.0	1	0	53.1000	S	C
4	0	3	male	35.0	0	0	8.0500	S	NaN
'''
### Changing a single Value (Option 2 with iloc)

titanic.iloc[1,3] = 41
titanic.head()

'''
    survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
0	0	3	male	22.0	1	0	7.2500	S	NaN
1	1	1	female	41.0	1	0	71.2833	C	C
2	1	3	female	26.0	0	0	7.9250	S	NaN
3	1	1	female	35.0	1	0	53.1000	S	C
4	0	3	male	35.0	0	0	8.0500	S	NaN
'''
### Changing multiple values in a column (Option 1 with loc)

titanic.loc[1:3, 'age']
# 행의 1,2,3 (loc은 기본적으로 문자열이기때문에 range나 iloc처럼 바로 앞의 숫자에서 멈추지 않고, 여기서 찾는 원리는 string이다.)
# 그 중에서도 'age'에 속해있는 값을 출력해줘라.

'''
1    41.0
2    26.0
3    35.0
Name: age, dtype: float64
'''
# 따라서 행을 한번에 2개 이상으로 선탠ㄱ하는 multiple values에 대해서는

titanic.loc[1:3, 'age'] = 42
titanic.loc[1:3, 'age']

'''
1    42.0
2    42.0
3    42.0
Name: age, dtype: float64
'''

# 이렇게 변형을 줄 수 있다.
### Changing multiple values in a column (Option 2 with iloc)

titanic.iloc[1:4,3 ]

'''
1    42.0
2    42.0
3    42.0
Name: age, dtype: float64
'''

# iloc의 경우는 loc과 다르게 int로써 움직이기때문에 1번 인덱스부터(0번부터 시작) 4번의 바로 앞 인덱스인 3번 인덱스까지 슬라이싱 해줘라.
# 열의 값도 3번 열 인덱스의 값을 가져와 줘라. (0부터 시작)를 의미한다.
titanic.iloc[1:4, 3] = [43,44, 45]
# 변경될 값이 1개면 전체다 그 값으로 바뀔것이고
# 리턴되는 길이만큼을 변형시켜주면 각각 변형을 일으킨다.
# 하지만 중간어딘가의 값을 입력하면 (이를테면 2개)
# ValueError: Must have equal len keys and value when setting with an iterable
# 라는, 반드시 길이값과 공평한 값을 입력하라는 메세지가 뜬다.
titanic.head()
### Changing multiple values in a column (Option 3 with boolean indexing)

index_babies = titanic.loc[titanic.age < 1, 'age'].index
# titanic.age가 1보다 작은 값의 인덱스를 주세요.

# Int64Index([78, 305, 469, 644, 755, 803, 831], dtype='int64')
titanic.loc[titanic.age < 1, 'age']
# titanic.age가 1보다 작은 값의 인덱스와 값을 반환해 주세요.

'''
78     0.83
305    0.92
469    0.75
644    0.75
755    0.67
803    0.42
831    0.83
Name: age, dtype: float64
'''
# 1 미만의 숫자들을 1로 무조건 올림처리 해주려면 이렇게 하면 된다. (계산의 편의를 위해서)
# 컬럼 값을 코테이션처리 안해주면 NameError: name 'age' is not defined 라는 에러가 발생된다.
titanic.iloc[index_babies] = 1

titanic.iloc[index_babies]

# 원하는대로 변경 되었다.


## Changing multiple values in a row

titanic.loc[0,'survived':'sex']
# 열(컬럼) 슬라이싱

'''
survived       0
pclass         3
sex         male
Name: 0, dtype: object
'''
# 0번째 행의 값에 대해서 survived 부터 sex까지의 값은 3개다.
# 이 3개의 값에 대하여 = 뒤의 값을 대체해줘라.
titanic.loc[0,'survived': 'sex'] = [1,1,'female']
titanic.head()

# 값 변경에 성공했다.
### Changing multiple values in multiple rows/columns

# 0이라는 모든 값에 대해서 'Zero'로 변경해줘라.
titanic.replace(0, 'Zero')

# 변경 되었다.


여기의 영역은 "해서는 안되는 방법"이다.

주의하자.

## How you should NOT do it (Part 1)

import pandas as pd
titanic = pd.read_csv('titanic.csv')

age = titanic.age
age[1]
# 38.0
# 경고 메세지가 뜬다. 그리고 값 변경엔 성공한다.
age[1] = 40
age[1]
# 40.0

<<여기서부터는 약간 영어로 메모 했었다.

한글로 타자 변경하기가 귀찮아서;>>

titanic.age[1] = 41 #This is Chained Indexing

'''
/var/folders/5g/z1vg08pd2_97rjb0w3yzhl4m0000gn/T/ipykernel_2918/64813537.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  titanic.age[1] = 41 #This is Chained Indexing
'''
# not Chained indexing
titanic.loc[1, 'age'] = 41

# what is deferent chained indexing compare nomal
# coming waring notice or not.
# so generally should use none chain indexing is better way
# even though both way can change as i want, whether chained indexing or not
# first of all, if i need use DataFrame, then should be step by step
col = titanic[['sex', 'age']]
# that is choose columns among titanic
col.head()

'''
    sex	age
0	male	22.0
1	female	41.0
2	female	26.0
3	female	35.0
4	male	35.0
'''
# i want try choose rows index among titanic, but can't by Key Error
titanic[[0, 4]]

# KeyError: "None of [Int64Index([0, 4], dtype='int64')] are in the [columns]"

# so, i can got information from this test
# DataFramename[[values]] for columns
# i want choose row index = 1, column index = 1
col.iloc[1,1]
# 41.0
col.iloc[1,1] = 43
col
'''
    sex	age
0	male	22.0
1	female	43.0
2	female	26.0
3	female	35.0
4	male	35.0
...	...	...
886	male	27.0
887	female	19.0
888	female	NaN
889	male	26.0
890	male	32.0
891 rows × 2 columns
'''

# and changed value


여기의 영역도 '해서는 안되는 방법' 이다.

주의 하자.

## How you should NOT do it (Part 2)

import pandas as pd
titanic = pd.read_csv('titanic.csv')
index_babies = titanic[titanic.age < 1].index
# Int64Index([78, 305, 469, 644, 755, 803, 831], dtype='int64')
titanic[titanic.age < 1]['age']

'''
78     0.83
305    0.92
469    0.75
644    0.75
755    0.67
803    0.42
831    0.83
Name: age, dtype: float64
'''
# in here case, used indexing twice, so did chain indexing
titanic[titanic.age < 1]['age'] = 1
titanic.loc[index_babies, :]

# oh those values dose not changed

'''
/var/folders/5g/z1vg08pd2_97rjb0w3yzhl4m0000gn/T/ipykernel_2918/1166346116.py:1: SettingWithCopyWarning: 
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

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  titanic[titanic.age < 1]['age'] = 1
        survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
78	1	2	male	0.83	0	2	29.0000	S	NaN
305	1	1	male	0.92	1	2	151.5500	S	C
469	1	3	female	0.75	2	1	19.2583	C	NaN
644	1	3	female	0.75	2	1	19.2583	C	NaN
755	1	2	male	0.67	1	1	14.5000	S	NaN
803	1	3	male	0.42	0	1	8.5167	C	NaN
831	1	2	male	0.83	1	1	18.7500	S	NaN
'''
titanic['age'][titanic.age < 1] = 1
titanic.loc[index_babies]

# what the..
# for my sight same as before, this one also twice sliced, before one also twice sliced
# but why before one didn't change and only change now?

'''
/var/folders/5g/z1vg08pd2_97rjb0w3yzhl4m0000gn/T/ipykernel_2918/2516220829.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  titanic['age'][titanic.age < 1] = 1
    survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
78	1	2	male	1.0	0	2	29.0000	S	NaN
305	1	1	male	1.0	1	2	151.5500	S	C
469	1	3	female	1.0	2	1	19.2583	C	NaN
644	1	3	female	1.0	2	1	19.2583	C	NaN
755	1	2	male	1.0	1	1	14.5000	S	NaN
803	1	3	male	1.0	0	1	8.5167	C	NaN
831	1	2	male	1.0	1	1	18.7500	S	NaN
'''
titanic[['sex', 'age']]

'''
    sex	age
0	male	22.0
1	female	38.0
2	female	26.0
3	female	35.0
4	male	35.0
...	...	...
886	male	27.0
887	female	19.0
888	female	NaN
889	male	26.0
890	male	32.0
891 rows × 2 columns
'''
# 변수 변경이 먹는지 시도
titanic[['sex', 'age']][titanic.age == 1]['age'] = 0

titanic.loc[index_babies]

# this measure also didn't changed values


여기서 부터는 다시 올바른 코드 표기

 

## View vs Copy
### Slicing a DataFrame / Creating a view on the original DataFrame

import pandas as pd
titanic = pd.read_csv('titanic.csv')
age = titanic.age
age.head()

'''
0    22.0
1    38.0
2    26.0
3    35.0
4    35.0
Name: age, dtype: float64
'''
# 원본 데이터 프레임의 뷰인지 복사본인지 확인하는 메서드
age._is_view
# True
# 무언가의 복사본인지 확인하는 메서드
age._is_copy is None

# True
age[1] = 40
# 경고 출력 됨
# 하지만 age변수와 titanic 변수의 값 모두가 변경 되었음.
### Slicing a DataFrame / Creating a copy of the original DataFrame

df_baby = titanic[titanic.age < 1]

df_baby._is_view
# False
# False 가 나왔다는 것은 무언가의 복사본이라는 뜻이다.
df_baby._is_copy is None
# False

# copy 가 아니다 의  False 이기 때문에 copy 인것이다.
df_baby._is_copy()

'''
	survived	pclass	sex	age	sibsp	parch	fare	embarked	deck
0	0	3	male	22.0	1	0	7.2500	S	NaN
1	1	1	female	40.0	1	0	71.2833	C	C
2	1	3	female	26.0	0	0	7.9250	S	NaN
3	1	1	female	35.0	1	0	53.1000	S	C
4	0	3	male	35.0	0	0	8.0500	S	NaN
...	...	...	...	...	...	...	...	...	...
886	0	2	male	27.0	0	0	13.0000	S	NaN
887	1	1	female	19.0	0	0	30.0000	S	B
888	0	3	female	NaN	1	2	23.4500	S	NaN
889	1	1	male	26.0	0	0	30.0000	C	C
890	0	3	male	32.0	0	0	7.7500	Q	NaN
891 rows × 9 columns
'''
# 익숙해져서 titanic 변수를 복사해왔다는 것을 알 수 있지만
# 실질적으로 정확히 "어떤 변수를"복사해왔는지는 알 수 없고,
# 어떤 내용을 복사 했는지를 보여주는 함수다.
df_baby.age = 1
# 경고문이 뜬다.
# 하지만 결과적으로 df_baby의 값과, 카피의 원본인 titanic의 값이 함께 변경된다.


## If you want to work with and manipulate the whole DataFrame
## avoid chained Indexing!!

import pandas as pd
titanic = pd.read_csv('titanic.csv')

titanic.iloc[1,3] = 40
titanic.head()
# 이부분의 구성을 잘 파악하자.
# 그렇지 않으면 에러 범벅으로 얼룩진다.
babies = titanic.loc[titanic.age < 1].index

# 이부분의 구성도 마찬가지
titanic.loc[babies, 'age'] = 1
titanic.loc[babies]
## make a copy with .copy()

import pandas as pd
titanic = pd.read_csv('titanic.csv')
age = titanic.age.copy()

age[1] = 40

age.head()

'''

0    22.0
1    40.0
2    26.0
3    35.0
4    35.0
Name: age, dtype: float64
'''
baby_ages = titanic.loc[titanic.age < 1, ['age', 'sex']].copy()
baby_ages

'''
	age	sex
78	0.83	male
305	0.92	male
469	0.75	female
644	0.75	female
755	0.67	male
803	0.42	male
831	0.83	male
'''
baby_ages['age'] = 1

baby_ages

'''
    age	sex
78	1	male
305	1	male
469	1	female
644	1	female
755	1	male
803	1	male
831	1	male
'''
index_babies = titanic.loc[titanic.age < 1, 'age'].index
titanic.loc[index_babies]
'''
결론적으로는
1. 원본 데이텨 프레임을 바꾸기 위해서는
체인 인덱스를 피하고, copy 메소드를 이용하지 않고 인덱싱하는게 좋다.

2. 원본 데이터 프레임을 바꾸지 않고, 슬라이스된 데이터 프레임만 이용해 작업하기 위해서는
체인 인덱싱은 피하고, copy 메소드는 이용해서 인덱싱하는게 좋다.

정확히는 모르겠지만, 체인인덱싱을 피하려면
loc, iloc을 사용해서 접근하는게 맞는것 같다.
'''
300x250