[debugging] Expected 2D array, got 1D array instead

찾아라 버그!

sklearn을 사용하다 보면 제목과 같은 에러 메시지를 종종 볼 수 있다. 다행히 개발자분들 께서 친절히 에러 메시지에 해결책을 써두고는 한다:

from sklearn.impute import SimpleImputer

imp = SimpleImputer()
imp.fit_transform([1,2,3])
ValueError: Expected 2D array, got 1D array instead:
array=[1. 2. 3.].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.

fit_transform이 2d array를 받아야 하나 1d array (이 경우는 단순한 list)를 집어 넣어서 그런건데, 본인의 상황에 따라 맞는 모양의 2d array로 바꿔주면 해결 할 수 있다.

근데 왜?

첫줄에서 말했다시피 해결책은 에러에 써져 있는데... 왜 이런 글을 쓰게 되었냐면,

첫번째로 위의 예시와 같이 list를 넣었다면 [1,2,3].reshape(-1, 1) 으로 해결할 수가 없다.

reshapenumpy.ndarray의 메소드이므로, 뭐야 이거 안되는데 라는 상황으로 갈 수 있다.

둘째로는, 이런식으로 해결책을 제공하는 경우에는 잠깐 코드를 수정하고 넘어 가버리는 경우가 대다수고, 실질적으로 무엇이 왜 잘 못 되었는가 또는 numpy.reshape이 왜 문제를 해결하는가 등에 대한 추가적인 고찰이 전혀 없을 수 있다. reshape인데 (-1, 1) 또는 (1, -1)은 무엇인가, 그런 것들을 오늘 짚고 넘어가 보려 한다.

원론적인 이슈

일단 기본적으로, sklearn은 거의 모든 경우에서 자신의 인풋 X가 2d로 들어 올 것으로 상정하고 있다. 당장 예시의 sklearn.impute.SimpleImputer의 설명만 보아도 출처:

X : {array-like, sparse matrix}, shape (n_samples, n_features)

Input data, where n_samples is the number of samples and n_features is the number of features.

n_samples x n_features로 이루어지는 array를 기대하고 있는것을 알 수 있다.

근데 여기다가 1d array 또는 list 를 주면? 이게 하나의 feature에 여러개의 samples이 있는건지, 여러개의 features에 하나씩의 sample이 있는건지 알 수가 없다. 그러니 자꾸 당신에게 되묻는거다, 친절한 설명과 함께.

numpy 해결책

에러 메시지를 천천히 읽어 봤다면 (또는 이 글을 열심히 읽었다면) 에러에 해결책이 존재한다. 만약 넣었던 Xnumpy.ndarray라면[^1] 바로 자신의 상황에 맞게 X.reshape(-1, 1)또는 X.reshape(1, -1)로 넣어주면 된다. reshape자체는 in-place 작업이 아니므로, 새로 변수 지정을 해주거나 fit에 바로 넣어주도록 한다. 무슨 말이냐고?

import numpy as np
from sklearn.impute import SimpleImputer

arr = np.array([1,2,3])
imp = SimpleImputer()
imp.fit_transform(arr)
# Expected 2D array...
# 아하, reshape 쓰라고?
arr.reshape(-1, 1)
imp.fit_transform(arr)
# Expected 2D array...
# 뭐여

위의 상황을 보면, reshape을 하긴 했는데, 정작 결과물을 재지정 또는 신규지정을 안해주니 똑같은 에러가 반겨준다. (의외로 이런 질문이 자주 보인다.)

재지정해주면 에러가 싹 사라진다.

arr = arr.reshape(-1, 1)
imp.fit_transform(arr)
# 또는
imp.fit_transform(arr.reshape(-1, 1))

array([[1.],
       [2.],
       [3.]])

근데 전 list 인데요..

list.reshape은 당연히 존재하지 않으므로 위의 해결책으로는 할 수 없다.

[1,2,3].reshape(-1, 1)
# AttributeError: ...

그렇지만 numpy가 누군가. 그렇게 호락호락한 라이브러리가 아니다. 당연히 독립함수도 있다. numpy.reshape을 쓰면 리스트도 얼마든지 바꿔진다!

import numpy as np

np.reshape([1,2,3], (-1, 1))

array([[1],
       [2],
       [3]])

혹시 numpy.reshape으로도 안바뀌는 데이터가 있다면 제보를 부탁한다.

번외: (-1, 1)? (1, -1)?

먼저 sklearn의 설명을 다시 한번 보자.

X : {array-like, sparse matrix}, shape (n_samples, n_features)

Input data, where n_samples is the number of samples and n_features is the number of features.

우리의 목표는 shape이 (int, int)로 이루어진 2D array를 만드는 것이다. 이 작업을 해주는 녀석이 numpy.reshape인데, 여기에 (-1, 1) 또는 (1, -1)을 넣는것이 해결책이였다. 근데 이게 뭐냐고..?

newshape : int or tuple of ints

The new shape should be compatible with the original shape. If an integer, then the result will be a 1-D array of that length. One shape dimension can be -1. In this case, the value is inferred from the length of the array and remaining dimensions.

numpy.reshape으로 변환하려는 녀석의 새로운 shape을 지정해 준다!

물론 음수가 아니라 모두 양수로도 가능하다 (3,2) 라던지. 그러나 이 경우에는, newshape[0] * newshape[1]이 기존에 가지고 있는 데이터의 갯수와 일치하여야 한다. 미리 알고 진행하면 좋지만, 그렇지 않을 수도 있으니, -1이 등장하게 된다.

-1은, 다른 newshape의 element들에 따라 결정되는데, (-1,1)이라고 하는것은, feature의 갯수는 1개이니, 나머지는 알아서 정해라, 정도로 해석될 수 있다. 이에 따라 우리의 (3,) array가, (3, 1)reshape되며, sklearn에서 바라던 2D로 정상적으로 바뀐다는 것을 알 수 있다.

[^1]: 헷갈린다면 type(X)로 확인하자.