MLOps엔지니어

ABC 부트캠프 AI파일럿 1기 MLOps 4일차_NumPy 배열 연산과 pandas 데이터 파악하기

ideas9064 2026. 8. 2. 14:28

01. NmnPy 기본 이해

NumPy의 핵심 자료형은 ndarray이다.
리스트와 달리 하나의 배열 안에는 같은 자료형만 담을 수 있다. 수치 연산에 특화된 자료구조이므로 자료형이 섞이면 연산이 성립하지 않기 때문이다.

score_list = [80,90,100]
score_np = np.array(score_list)

print(type(score_list))
print(type(score_np))

출력만으로도 두 자료구조를 구분할 수 있다.

리스트는 원소 사이에 콤마가 표시되지만 ndarray는 콤마 없이 [ 80 90 100] 형태로 출력된다. 다만 정확한 판별은 type()으로 해야 한다.

배열의 기본 속성은 소괄호 없이 호출한다. 소괄호가 붙으면 기능을 수행하는 함수·메서드이고, 붙지 않으면 객체가 고유하게 가진 속성(어트리뷰트)이다.

 

score_np = np.array([80,90,100])
print('차원',score_np.ndim)
print('형태',score_np.shape)
print('원소 개수',score_np.size)
print('자료형',score_np.dtype)

출력은 차원 1, 형태 (3,), 원소 개수 3, 자료형 int64이다.

1차원 배열의 shape은 뒷자리를 생략하므로 (3,)으로 표기되며, 앞의 3은 데이터 개수를 의미한다.

 

배열 생성 함수는 네 가지를 다루었다.

print(np.array([10,20,30,50])) # 리스트 값(특정 값)을 지정해서 배열 생성
print(np.zeros(5)) # 5개의 값을 0으로 채워서 생성
print(np.ones(5)) # 5개의 값을 1로 채워서 생성
print(np.arange(1,11)) # 범위 값을 채워서 생성 1부터 10까지 데이터를 만들어서 배열 생성
함수 기능
array() 리스트 등 기존 값을 배열로 변환
zeros() 지정한 개수만큼 0으로 채운 배열 생성
ones() 지정한 개수만큼 1로 채운 배열 생성
arange() 지정한 범위의 연속 값으로 배열 생성

zeros()ones()가 생성하는 배열의 dtypefloat64이다. 실수형이 정수형을 포함하므로 초기값 배열은 기본적으로 실수형으로 만들어진다. 머신러닝 단계에서 학습 데이터의 초기 배열을 생성할 때 사용된다는 용도가 함께 설명되었다.


02. 리스트와 NumPy 연산 비교

동일한 * 2 연산이 자료구조에 따라 다르게 동작한다.

score_list = [80,90,100]
score_np = np.array(score_list)

print('리스트 * 2:', score_list * 3) #리스트의 곱셈은 리스트가 가지고 있는 요소를 복사하기(문자열 곱셈과 동일)

print('NumPy배열 * 2: ',score_np * 2) # NumPy 배열의 곱셈은 각 요소에 곱셈 연산
print('NumPy배열 + 10: ',score_np + 10)

리스트의 곱셈은 문자열 곱셈과 동일하게 요소를 복사하고, 덧셈은 두 리스트를 연결한다. 반면 NumPy 배열의 곱셈과 덧셈은 각 원소에 대한 연산으로 처리된다. 수십만 건 데이터에 일괄 연산을 적용해야 할 때 배열로 변환한 뒤 한 줄로 처리할 수 있다는 점이 차이의 실질적인 의미이다.


03. 서울 최고기온 분석

03-1. NumPy 배열로 변환

CSV를 읽는 단계에서는 리스트에 담고, 반복문이 끝난 뒤 배열로 변환한다.
append()가 반복문 안에 있고 배열 변환은 반복문 밖에 있어야 한다는 들여쓰기 위치를 강조하였다.

date_list =[]
max_temp = []

with open(FILE_PATH, 'r', encoding='euc-kr') as f:
    data = csv.reader(f)
    header = next(data)
    for row in data:
        # 최고기온이 비어 있으면 해당 행을 건너뜁니다.
        if row[-1] == '':
            continue
        date_list.append(row[0])
        max_temp.append(float(row[-1]))

# 리스트를 NumPy 배열로 변환
dates = np.array(date_list)
max_temps = np.array(max_temp)

print('날짜 배열 형태:' , dates.shape)
print('최고기온 배열 형태:' , max_temps.shape)

결측치를 제외한 결과는 두 배열 모두 (39462,)이다. 두 배열의 건수가 일치해야 같은 인덱스로 값을 대응시킬 수 있으므로 shape 확인은 필수 절차에 해당한다.

03-2. argmax()로 위치 찾기

max_index = np.argmax(max_temps) #변수명 지정할 때 위치를 가리키는 것하는 idex를 붙여서 하면 좋음.
max_index  # '39292' 위치값

print('서울의 최고기온이 가장 높았던 날:', dates[max_index])
print('서울의 최고기온:', max_temp[max_index])

np.argmax()가 반환하는 값은 최댓값 자체가 아니라 최댓값이 저장된 위치(인덱스)이다.
반환값은 39292이고, 이 인덱스를 datesmax_temps에 각각 넣어 2018-08-01, 39.6℃를 얻는다.
날짜 배열과 기온 배열이 같은 순서로 구성되어 있기 때문에 하나의 인덱스로 두 값을 동시에 조회할 수 있다.

03-3. 기초 통계량

 

메서드 설명
mean() 평균값을 계산
max() 가장 큰 값을 반환
min() 가장 작은 값을 반환
std() 표준편차를 계산
print('평균 기온은 %.3f도' % max_temps.mean() )
print('가장 높은 최고 기온은 %.3f 도' % max_temps.max())
print('최저 기온은 %.3f 도' % max_temps.min())
print('기온의 표준편차: %.3f' % max_temps.std())

04. 조건에 맞는 기온 추출 (Boolean Indexing)

비교 연산자를 배열에 적용하면 각 원소에 대한 True/False 배열이 생성되고, 이 배열을 인덱스로 사용하면 조건을 만족하는 값만 추출된다.

hot_days = max_temps >= 37 #  조건에 만족하는 True, false 결과가 들어감. 비교연산자 사용으로 true 아니면 false로 값이 출력됨
hot_days[0:10]
type(hot_days)

print('37도 이상인 날짜: ', dates[hot_days])
print('38도 이상인 기온:', max_temps[hot_days])
print('37도 이상인 날의 수: ', np.sum(hot_days))

시각화 scatter 차트로 처리.
matplotlib은 x축과 y축이 모두 수치형이어야 하므로, 문자열인 날짜를 그대로 넣을 수 없다.
먼저 np.arange()로 수치 인덱스를 만들어 차트를 그린 뒤 xticks()로 축 라벨만 날짜 문자열로 교체하는 순서를 따른다.

# 1. 37도 이상의 날짜와 온도를 담을 변수(nparray)를 선언
hot_dates = dates[hot_days]
hot_temps = max_temps[hot_days]

x = np.arange(len(hot_dates))

# 2. 차트 시각화(scatter차트) plt 라이브러리는 x,y축은 모두 수치형
plt.figure(figsize =(15,5))

plt.scatter(x,hot_temps)
plt.xticks(x, hot_dates, rotation=-60) # x축의 값을 hot_dates의 값으로 교체하고 글씨의 방향을 90도 회전
plt.xlabel('날짜')
plt.ylabel('기온')
plt.title('서울의 최고 기온 기준 37도 이상 폭염일')
plt.grid(alpha=0.3) # 30% 투명도 x축과 y축의 경계를 그림.
plt.show()

05. 특정 연도 기온 시각화

NumPy는 수치 연산 외에 문자열 처리 함수도 np.char 하위에 제공한다.
문자열 슬라이싱으로 인덱스를 하드코딩하는 대신 함수를 사용하면 코드 품질이 개선된다.

year_2018 = np.char.startswith(dates,'2018') # 각 요소의 시작위치부터 2018로 시작하는 문자열 찾기 -> boolean indexing
temp_2018 = max_temps[year_2018] #2018 최고기온 데이터 추출
dates_2018 =dates[year_2018]     # 2018 날짜 데이터 추출

print('2018년도 날짜 데이터 수: ' , len(dates_2018))
print(dates_2018[0:5])

np.char.startswith()의 반환값도 True/False 배열이므로 Boolean Indexing과 동일하게 동작한다.


06. ★ 리스트 컴프리헨션과 월별 기온 비교

리스트 컴프리헨션은 반복문, 표현식 처리, append()를 한 줄로 합치는 문법이다.

[표현식 for 변수 in 반복가능한객체]
result = []
for 변수 in 반복가능한객체:
    result.append(표현식)

실습 문제(연도만 추출)

year = [date[:4]for date in dates]
year

월 추출과 월별 비교

months = np.array([date[5:7] for date in dates])

print(months) # 전체 날짜에서 월만 추출한 ndarray

# boolean indexing을 통한 1월 8월 추출
jan_temp = max_temps[months == '01']
aug_temp = max_temps[months == '08']

print('1월의 평균 최고 기온: ', jan_temp.mean())
print('8월의 평균 최고 기온:', aug_temp.mean())
print('1월의 데이터 수: ', jan_temp.size)
print('8월의 데이터 수:', aug_temp.size)

리스트 컴프리헨션의 결과를 np.array()로 감싸 ndarray로 만든 뒤 조건식을 대괄호에 직접 넣었다.

분포 비교

plt.figure(figsize=(8,5))
plt.boxplot([jan_temp, aug_temp], labels =['1월','8월'])
plt.title('1월과 8월의 최고 기온 분포')
plt.xlabel('월')
plt.ylabel('최고 기온')
plt.grid(axis='y',alpha=0.3)
plt.show()

--grid(axis='y')로 격자를 y축 방향으로만 그려 기온 값을 읽기 쉽게 함.--




07. Pandas 데이터 파악할 때 사용하는 함수

오후 실습 데이터는 미국 환경청(EPA)의 자동차 연비 데이터셋(mpg)이다. 1999년부터 2008년까지 매년 출시된 인기 차량을 선별한 표준 예제 데이터셋이다.

csv.reader()와 달리 read_csv()는 첫 줄을 컬럼으로 자동 인식하고 두 번째 줄부터 데이터로 처리한다. 다만 컬럼 행이 없는 CSV는 첫 데이터 행이 컬럼으로 잡히는 문제가 발생하므로 원본 구조 확인이 선행되어야 한다.

데이터를 파악할 때 사용하는 함수

함수 기능
head() 앞부분 출력
tail() 뒷부분 출력
shape 행, 열 개수 출력
info() 변수 속성 출력
describe() 요약 통계량 출력

 

mpg.describe() # 수치형 컬럼들의 요약 통계량 제공
mpg.describe(include='all')

describe()는 수치형 컬럼에 대해 count, mean, std, min, 25%, 50%, 75%, max를 한 번에 제공한다.
컬럼 정보는 다음과 같다.


08. 함수, 패키지 함수, 메서드, 어트리뷰트

구분 설명 사용 형태 예시
내장 함수 파이썬에서 기본으로 제공 함수(데이터) print(), len(), type(), sum()
패키지 함수 라이브러리에서 제공 패키지.함수(데이터) np.array(), np.arange(), np.argmax()
메서드 객체가 가지고 있는 기능 객체.메서드() max_temps.mean(), mpg.head()
어트리뷰트 객체가 가진 고유 속성 객체.속성 mpg.shape, score_np.dtype
# 1) 내장함수 : import 하지 않고 사용 할 수 있는 함수
sum(mpg['year'])

# 2) 패키지 함수 : import 해서 패키지명과 같이 호출하는 함수
pd.read_csv('/content/mpg.csv')

# 3) 메서드 : 객체를 통해서 (내가 생성한 변수명) 사용할 수 있는 기능
mpg.head()

#) 어트리뷰트 : 객체(or 변수)가 가지고 있는 고유한 속성
mpg.shape

별 기준은 앞에 무엇이 붙는지이다. 아무것도 붙지 않으면 내장 함수, 패키지 별칭이 붙으면 패키지 함수, 직접 선언한 변수명이 붙으면 메서드이다. 소괄호 유무로 메서드와 어트리뷰트를 구분한다.


09. 컬럼명 바꾸기

전처리 전에 원본을 복사해 두는 절차가 선행된다.

df_mpg = pd.read_csv('/content/mpg.csv')
df_new = df_mpg.copy() #원본 데이터 보존이 핵심
df_new = df_new.rename(columns = {'manufacturer':'manufact'}) # {기존컬럼명 : 바꿀컬럼명}

rename()은 원본을 직접 수정하지 않고 변경된 결과를 반환하므로 다시 변수에 담아야 적용된다.

인자는 {기존컬럼명: 바꿀컬럼명} 형태의 딕셔너리이며, 콤마로 이어 여러 컬럼을 한 번에 변경할 수 있다.

 

변수명 규칙도 함께 제시되었다.

DataFrame 자료구조를 담는 변수에는 df_mpg, mpg_df처럼 df를 붙여 자료구조를 명시하는 것이 코드 리뷰 관례이다.


10. 파생 변수 만들기

수집이 끝난 데이터에 필요한 항목이 없을 때, 기존 컬럼을 계산해 새 컬럼을 만드는 것을 파생변수라고 한다. 머신러닝 단계에서 학습 성능을 높이기 위해 자주 사용된다.

10-1. 계산식으로 만들기

# 통합 연비 계산 - (도시 연비 + 고속도로 연비 ) / 2
df_mpg['total'] = (df_mpg['cty'] + df_mpg['hwy']) / 2

df_mpg.head()

DataFrame의 컬럼 접근은 딕셔너리와 동일한 구조이다. 해당 컬럼명이 없으면 새로 생성되고, 있으면 값이 대체된다. 반복문 없이 234개 행 전체에 계산이 일괄 적용된다.

10-2. 조건식으로 만들기

기준값은 임의로 정하지 않고 근거를 제시해야 한다. describe()total의 평균이 20.149573임을 확인하고, 히스토그램으로 분포가 정규분포에 가까운 형태임을 확인한 뒤 20을 기준으로 정하였다.

df_mpg['total'].describe() #요약 통계량
# 그래프로 표현
df_mpg['total'].plot.hist();

matplotlib을 임포트하지 않아도 pandas 자체 차트 기능으로 히스토그램을 그릴 수 있다. 코드 끝의 세미콜론은 축 객체가 출력되는 것을 막기 위한 표기이다.

import numpy as np

#total 값이 20 이상이면 pass, 20이하면 fail 부여
df_mpg['test'] = np.where(df_mpg['total'] >= 20, 'pass','fail'); #np.where(조건, 만족하면 처리, 만족하지 않을 경우 처리)
df_mpg.head()

np.where(조건, 참일 때 값, 거짓일 때 값)은 if-else 구조를 함수 하나로 대체하며, 234건 전체를 한 번에 처리한다는 점이 핵심이다.


11. 빈도표와 비율 시각화

df_mpg['test'].value_counts()

결과는 pass 128건, fail 106건.

```python
df_mpg['test'].value_counts().plot.bar(rot = 0); #rot =o 축 이름을 수평으로 만듬

rot은 rotation의 약자이며 막대 차트의 x축 라벨 기본 각도가 90도이므로 0으로 지정해 수평으로 바꾼다.

import matplotlib.pyplot as plt
df_mpg['test'].value_counts().plot.pie(autopct='%.3f%%');
plt.title('pass and fail')
plt.show

막대 차트는 절대 수치 비교, 파이 차트는 비율 비교를 담당한다.
128건과 106건의 차이가 전체에서 몇 퍼센트를 차지하는지는 절대값만으로는 판단하기 어려우므로 두 차트를 함께 제시한다는 관점이 설명되었다. autopct는 파이 차트에 표시할 비율의 소수점 자릿수를 지정한다. koreanize-matplotlib을 설치하지 않은 노트북이므로 제목은 영문으로 작성하였다.


12. 데이터 전처리 개요와 query()

pandas의 데이터 가공 메서드는 다음과 같이 정리되었다.

메서드 기능
query() 행 추출
df[] 열(변수) 추출
sort_values() 정렬
groupby() 집단별로 나누기
agg() 통계치 구하기
merge() 데이터 합치기(열)
concat() 데이터 합치기(행)

조건 추출에 앞서 대상 컬럼의 구성을 확인한다. 중복을 제거한 값의 종류는 unique()로 조회한다.

# category(차종)의 종류가 어떻게 구성되어 있는지 확인(중복제거 한 데이터 종류) -> category 컬럼 확인
df_mpg['category'].unique()

결과는 compact, midsize, suv, 2seater, minivan, pickup, subcompact의 7종이다.

df_mpg.query("category == '2seater'") # 여기선 [""]을 넣어야됨

query()의 조건식은 문자열로 전달한다. 조건식 내부에서 문자열 값을 비교할 때 작은따옴표를 쓰므로, 바깥쪽은 큰따옴표로 감싸야 한다. 추출 결과는 5건이다.

df_mpg.query("category != 'minivan'")
df_mpg.query("year > 2005")
df_mpg.query("category == 'minivan' & year >=2000")
df_mpg.query("category == 'minivan' |category == 'pickup' | category == '2seater'")
df_mpg.query("category in ['minivan','pickup','2seater']")

minivan은 11건이므로 부정 조건의 결과는 223건이고, year > 2005 조건은 117건이다. 논리 연산자는 텍스트 and가 아니라 기호 &|를 사용한다. 마지막 두 줄은 동일한 결과를 반환하며, 세 가지 이상의 값을 비교할 때는 in 연산자로 리스트를 전달하는 편이 간결하다. query()의 조건식 문법이 SQL의 WHERE 절과 거의 동일하므로 데이터베이스 연동 단계에서 그대로 재사용할 수 있다는 점이 언급되었다


15. 필요한 변수만 추출하기

대괄호 개수에 따라 반환되는 자료구조가 달라진다.

# 하나의 컬럼으로 구성되어 있으면 Series 자료 구조로 추출(1차원 배열)
type(df_mpg['category'])
type(df_mpg[['category']])

df_mpg['category']는 Series, df_mpg[['category']]는 DataFrame을 반환한다. Series는 컬럼명을 가진 1차원 배열에 해당하며, 화면 출력만으로는 두 구조를 구분하기 어렵다. 머신러닝 단계에서 컬럼 하나를 NumPy 배열로 변환할 때는 Series 형태가 사용되고, DataFrame 간 결합을 위해 형태를 유지해야 할 때는 대괄호를 두 번 감싼다.

# 여러개의 컬럼을 한번에 추출
df_mpg[['year','model','category']]

 

여러 컬럼을 동시에 추출할 때도 대괄호를 두 겹으로 사용한다.