데이터에 null값이 존재할때 즉 데이터 프레임에 빵꾸 나있을때에 그대로 머신에 넣으면 오류가 난다.. ( 아닌가..? 해본적이없다.. )
이를 해결하기위해 우리가 임의로 데이터를 넣자니 안될거같고.. 어떤 방법들이 있는지 알아보자..
일단 아래 나온 방법들이 가장 좋다라는 것은 아니다 언제나 데이터에 따라 머신에 따라 어떤 방법을 써야할지는 엔지니어가 결정해야한다.
pandas로 null값 확인하기
먼저 imputation을 하기전에 null이 있는지부터 확인을 해야한다.
방법은 isna 또는 isnull이 있다. 둘다 같은 함수.. 이름만 다른..
pd.isnull( dataFrame ).sum() 을 통해 결측치의 갯수를 확인 할 수 있다.
pd.isnull 을 통해 True / False로만 구성된 dataFrame생성
sum()을 통해 열별로 계산 ( True +1, False +0 )
sum()을 한번 더 하게 된다면 행으로도 계산하여 데이터 프레임 전체의 null값을 확인할 수 있다.
pd.isna( dataframe ) 을 통해 true/false 데이터프레임 생성sum() 을 통해 열별 합 계산
sum().sum()을 통해 전체 null갯수 확인
Sklearn.impute.Simplelmputer 을 통한 대체
simpleimputer을 통해서는 평균, 최빈, 중앙값등 간단한 대체가 가능하다.
라이브러리 사용을 위해 import
1. 평균값으로 대체 (Mean Imputation)
결측치가 존재하는 변수에서 결측되지 않은 나머지 값들의 평균을 내어 결측치를 대체하는 방법이다.
해당 값으로 대체 시 변수의 평균값이 변하지 않는다는 장점이 있지만, 많은 단점이 존재한다.
strategy 변수에 mean을 줌으로 평균으로 대체하게해줌
2. 중앙값으로 대체 (Median Imputation)
중간값은 데이터 샘플을 개수에 대해서 절반으로 나누는 위치의 값을 말한다.
데이터 샘플의 수가 짝수개일 때에는 중간에 위치한 두 값의 평균을 사용한다.
모든 관측치의 값을 모두 반영하지 않으므로지나치게 작거나 큰 값(이상치)들의 영향을 덜받는다.
3. 최빈값으로 대체 (Most-Frequent Imputation)
최빈값은 가장 많이 나온 값이다.
1. 새로운 값으로 대체 (Substitution)
아예 해당 데이터 대신에 샘플링 되지 않은 다른 데이터에서 값을 가져온다. (그렇다면 validation set에서도 쓰지 않고 아예 버리게 되는 셈인 건가?)
2. Hot deck imputation
다른 변수에서 비슷한 값을 갖는 데이터 중에서 하나를 랜덤 샘플링하여 그 값을 복사해오는 방법. 이 방법은 결측값이 존재하는 변수가 가질 수 있는 값의 범위가 한정되어 있을 때 이점을 갖는다. 또한 random하게 가져온 값이기 때문에 어느 정도 변동성을 더해준다는 점에서 표준오차의 정확도에 어느 정도 기여를 한다는 점이다.
3. Cold deck imputation
Hot deck imputation과 유사하게, 다른 변수에서 비슷한 값을 갖는 데이터 중에서 하나를 골라 그 값으로 결측치를 대체하는 방식이다. 다만 cold deck imputation에서는 비슷한 양상의 데이터 중에서 하나를 랜덤 샘플링하는 것이 아니라 어떠한 규칙 하(예를 들면, k번째 샘플의 값을 취해온다는 등)에서 하나를 선정하는 것이다. 이 경우 hot deck imputation 과정에서 부여되는 random variation이 제거된다.
4. Regression imputation
결측치가 존재하지 않는 변수를 feature로 삼고, 결측치를 채우고자 하는 변수를 target으로 삼아 regression task를 진행하는 것이다.데이터 내의 다른 변수를 기반으로 결측치를 예측하는 것이기 때문에 변수 간 관계를 그대로 보존할 수 있지만 동시에 예측치 간 variability는 보존하지 못한다. (회귀분석을 생각해보면 regression line은 random component가 존재하지 않는다. regression 값 그 자체로 존재한다. )
5. Stochastic regression imputation
regression 방법에 random residual value를 더해서 결측치의 최종 예측값으로 대체하는 방식. regression 방법의 이점을 모두 갖는데다 random component를 갖는 데에서 따르는 이점 또한 갖는다.
6. Interpolation and extrapolation (보간법, 보외법)
같은 대상으로부터 얻은 다른 관측치로부터 결측치 부분을 추정하는 것이다. 이 경우는 longitudinal data의 경우(어린이의 성장 과정을 추적하는 과정에서 얻은 키 데이터라든지 하는 경우)에만 가능할 것이다.
결론
위와 같이 많은 방법들이 있다. 물론 이 방법들로 진행해야 하는것은 없다.
하지만 서로 다른 방법에 따라 결과의 차이는 분명히 나타난다.
간단한 아래의 예시들을 비교해보면
타이타닉에서 나이의 null 값을 나이의 평균으로 넣는방법
타이타닉에서 나이의 null값을 이름 열을 통해서 대략적인 성별을 판단해 성별의 평균 나이로 넣는방법
size()의 결과와 0을 비교하는 것은 empty()를 호출하는 것과 본질적으로 똑같습니다. 하지만 size()는 항상 상수 시간에 수행된다는 보장이 없고, empty()는 항상 상수 시간에 수행됩니다.이유는 list의 splice 함수와 밀접한 관련이 있습니다. splice 함수는 객체의 복사를 하지 않고, list의 특정 요소들을 다른 list로 옮길 수 있는 함수입니다.
list의 size()가 상수 시간에 수행되도록 하려면, list 객체 내에 리스트의 요소가 몇 개 존재하는지를 담아두는 멤버를 하나 준비하고, 요소의 수를 변경시킬 수 있는 모든 경우에 이 멤버 변수의 값을 갱신 해야 합니다.
물론 splice 함수도 마찬가지로 요소의 수를 담는 멤버 변수를 갱신 해야 하고, 이를 위해서 splice가 호출될 때마다 옮긴 요소의 수를 세어야 하므로, splice 함수가 상수 시간에 수행되도록 만들 수가 없게 됩니다.
여러 STL 제품에서 list를 다르게 구현해 놓고 있습니다. 예상하고 있겠지만 구현을 맡은 개발자가 size와 splice 중 어디에 비중을 두고 있는지에 따라, splice가 상수 시간에 수행되는 대신 size가 상수 시간에 수행되지 않을 수도 있고 그 반대일 수도 있습니다.
지금 쓰는 라이브러리의 list에서 size()가 상수 시간에 수행된다고 하더라도 나중에 라이브러리를 교체하거나, 다른 플랫폼으로 포팅할 일이 생길 지도 모릅니다. 그러므로 size()의 결과를 0과 비교하기보다는 empty()를 호출하는 것이 좋습니다.
2. 단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다
단일 요소를 단위로 동작하는 멤버 함수(이하 단일 요소 함수)보다 요소의 범위를 단위로 동작하는 멤버 함수(이하 범위 멤버 함수)가 성능 면에서 더 낫습니다. 만일 단일 요소 함수를 사용하여 여러 요소를 삽입하려면, 반복문을 사용할 수 밖에 없습니다. 반복문을 사용하여 요소를 하나씩 삽입하면 타자량도 많고, 가독성도 그다지 좋다고 할 수 없습니다.
vector<Widget> v1, v2; // v1과 v2는 Widget을 담는 벡터라고
// 가정합시다.
...
// v1의 내용을 v2의 뒷쪽 반과
// 똑같이 만드는 가장 빠른 방법은
// 무엇일까요?
// 정답은 assign을 사용하여
// 다음과 같이 짧게 작성할 수 있습니다.
v1.assign(v2.begin() + v2.size() /2, v2.end());
// 반복문을 사용하면
v1.clear();
for(vector<Widget>::const_iterator ci = v2.begin() + v2.size() /2; ci != v2.end(); ++ci) {
만일 배열에 있는 데이터를 벡터의 앞 부분으로 옮긴다고 생각해 봅시다. 이 경우 단일 요소 함수를 쓰는 것 보다 범위 멤버 함수를 쓰는 것이 좋은 이유가 3 가지나 있습니다. 첫째, 단일 요소 함수는 배열의 멤버 수 만큼 호출되어야 하지만, 범위 멤버 함수는 딱 한 번만 호출되면 되므로 함수 호출 비용이 적게 듭니다. 물론 인라인 함수인 경우에는 차이가 없지만 모든 단일 요소 함수가 인라인인 것은 아닙니다.
둘째, 벡터에 들어 있던 기존의 데이터들을 미는 횟수에서 차이가 납니다. 단일 요소 함수는 배열의 요소들을 하나 씩 삽입하기 때문에 총 복사 횟수는 (배열의 총 요소 수) * (벡터에 들어 있던 요소의 수) 만큼이 됩니다. 반면 범위 멤버 함수의 경우 몇 개가 삽입되는지를 미리 알 수 있기 때문에, (벡터에 들어 있던 요소의 수) 만큼만 복사(딱 한번만 밉니다)가 일어납니다.
셋째, 메모리 할당에 관한 것입니다. 대부분의 경우 벡터는 메모리가 꽉 찰 때마다 자신의 용량을 두 배로 늘리도록 구현이 되어 있습니다. 즉 n개의 새 데이터 요소를 하나씩 삽입하려고 하면 메모리 할당을 log2n번이나 하게 되는 셈입니다. 반면 범위 멤버 함수를 쓰면 삽입할 요소의 수를 미리 알 수 있으므로 딱 한 번 필요한 메모리를 할당하면 됩니다.
vector에 대해서 설명드린 내용은 string에서도 동일하게 적용됩니다. deque의 경우에는 비슷하긴 하지만 vector나 string과는 다른 메모리 관리 방식을 취하고 있어서 "반복적인 메모리 재할당"에 관한 이야기는 맞지 않습니다. 하지만 불필요하게 빈번한 컨테이너 내 요소의 이동이나, 불필요한 함수 호출에 관한 이야기는 일반적으로 맞습니다.
list 역시, 범위 멤버 함수가 단일 요소 함수보다 수행 성능에서 우수합니다. 되풀이되는 함수 호출에 있어서는 역시 범위 버전이 좋습니다. 하지만 list는 노드 기반으로 동작하기 때문에 메모리 할당에 관한 사항은 딱 맞지 않습니다. 그 대신에 리스트의 노드를 연결하는 next 포인터와 prev 포인터 값이 불필요하게 되풀이해서 세팅되는 문제가 생깁니다.
최소한 표준 시퀸스 컨테이너에 대해서는, 단일 요소 버전의 삽입이냐, 범위 버전의 삽입이냐를 선택하는데 있어서 "프로그래밍 스타일"을 압도하는 많은 요인들을 내새울 수 있게 되었습니다. 그렇다면 연관 컨테이너에 대해서는 어떨까요? 단일 요소 버전의 insert에서 여전히 반복 함수 호출의 오버헤드가 있긴 하지만 딱 부러지게 효율이 어떻다라고는 말씀드리기 힘듭니다. 게다가 몇 가지 특수한 종류의 범위 삽입 함수들의 최적화의 여지를 가지고 있지만 이론적으로만 이러한 최적화가 존재합니다.하지만 연관 컨테이너에서 범위 멤버 함수를 쓴다고 해서 효율이 뒤진다든지 하는 것은 없으니 지금 쓰셔도 잃는 것은 없습니다.
굳이 효율 문제가 아니더라도, 타자수를 줄여주고 나중에 읽기도 편해 이해하기 좋기 때문에 연관 컨테이너에서도 범위 멤버 함수를 쓰는 것이 좋습니다.
범위를 지원하는 멤버 함수는 어떤 것인지 미리 알아놓고 정리해 두면, 나중에 이것들을 사용할 기회를 포착하기가 매우 쉬울 것입니다.
STL에 속하지 않은 표준 컨테이너:배열(C++ 배열), bitset, valarray, stack, queue, priority_queue.
이렇게 많은것중 한가지만 알면 당연히 안된다.. 각각의 특징을 이해하고 언제 어떤것을 사용할지를 정리하는것이 이 글의 목표....
우선, STL 컨테이너는 연속 메모리(continuous-memory) 컨테이너와 노드 기반(node-based) 컨테이너로 나눌 수 있다.
연속 메모리 컨테이너( 배열 기반 컨테이너 )
동적 할당된 하나 이상( 대개 하나 )의 메모리 단위( chunk )에다가 데이터 요소를 저장해 두는 컨테이너입니다. 새 요소가 삽입되거나 이미 있던 요소가 지워지면(erase), 같은 메모리 단위에 있던 다른 요소들은 앞 혹은 뒤로 밀려나면서 새 요소가 삽입될 공간을 만들던지, 지워진 공간을 메웁니다. 이러한 "밀어내기" 때문에 수행 성능의 발목을 잡을 수 있고, 예외 안전성(exception safety)에도 영향을 미칩니다. 여기에 속하는 컨테이너는 vector, string, deque입니다. 비표준 컨테이너인 rope 역시 연속 메모리 컨테이너입니다.
노드 기반 컨테이너
동적 할당된 하나의 메모리 단위에다가 하나의 요소만을 저장합니다. 컨테이너 요소를 삽입 혹은 삭제했을 때 노드의 포인터만이 영향을 받지, 노드의 내용은 그대로입니다. 따라서, 삭제나 삽입이 일어났다고 해도 나머지 요소들이 밀려난다든지 하는 일이 없습니다.
연결 리스트를 나타내는 컨테이너, 즉 list와 slist가 노드 기반이고, 표준 연관 컨테이너 모두가 노드 기반 입니다(이것들은 전형적으로 균형 트리(balanced tree)로 구현되어 있습니다).
이제는 "어떤 상황에 어떤 컨테이너를 쓰면 가장 좋을까?"에 관련된 문답을 수월하게 정리할 수 있을 것입니다.
인덱스를 통한 요소 삽입 가능해야한다. -> 시퀀스 컨테이너
요소들의 순서에 관심 없다 -> 해쉬 컨테이너
반복자 타입에 대한 구분
임의 접근 반복자 : vector, deque, string
양방향 반복자 : slist와 해쉬 컨테이너는 쓸 수 없다
요소 삽입 삭제시 다른 요소가 밀려나는일 없어야한다 : 연속메모리 컨테이터는 불가
C의 데이터 타입과 메모리 배열 구조적으로 호환되어야 한다 : vector 밖에 쓸 것이 없다.
탐색 속도가 중요하다 : 해쉬 컨테이너, 정렬된 vector, 그리고 표준 연관 컨테이너
컨테이너의 참조 카운팅이 신경 쓰이나요? 그렇다면 string 가까이에는 가지 않는 것이 좋습니다. 많은 string 코드가 참조 카운팅이 되도록 구현되어 있습니다. 이럴 때 vector<char>를 쓰는 것입니다.
삽입 삭제가 안정적 : 노드 기반 컨테이너를 고려해 보시기 바랍니다.
트랜잭션적인 삽입이 여러 개의 요소(범위로 주어집니다)에 대해 이루어져야 할 경우에는 list를 선택합니다.
반복자, 포인터, 참조자가 무효화(포인터가 가리키고 있던 메모리의 실제 내용이 없어지는 일을 뜻한다)되는 일을 최소화해야 하나요? 이런 경우에는 노드 기반 컨테이너를 사용하기 바랍니다. 노드 기반 컨테이너는 노드 삽입과 삭제가 일어나도 기존의 반복자나 포인터 혹은 참조자가 무효화되지 않기 때문입니다(가리키고 있는 요소를 삭제하지 않는 한 말이죠). 반대로 연속 메모리 컨테이너는 전체적인 메모리 재할당이 빈번하게 일어나기 때문에 반복자나 포인터, 참조자가 무효화되기 쉽습니다.
임의 접근 반복자를 지원하는 시퀸스 컨테이너가 필요한데, 요소 삭제가 일어나지 않고 요소 삽입이 컨테이너의 끝에서만 일어나는 한, 포인터와 참조자가 무효화되지 않는 것이 필요한가요? 아주 특별한 경우이긴 하지만, 어쩌다 이런 경우를 만난다면 deque가 정답입니다. deque는 요소 삽입이 끝에서 일어날 때 반복자만 무효화되는 재미있는 컨테이너입니다(STL 컨테이너 중 포인터와 참조자를 무효화시키지 않고 반복자만 무효화되는 것은 deque가 유일합니다).