5 분 소요

☀️공지 사항☀️ 해당 게시글은 SK 쉴더스 루키즈, 생성형AI 활용 사이버보안 전문인력 양성과정 을 수업을 듣고 정리한 글입니다. 자세한 교육 정보는 SK 쉴더스 홈페이지 를 확인해주세요.

# 학습 내용 요약 (25/11/04)

이번 학습 과정은 데이터 분석의 핵심적인 두 축인 기초(NumPy/Pandas 구조)와 응용(API 통신 및 데이터 정규화)을 종합적으로 다루었습니다. 특히 외부 API를 통해 얻은 복잡한 JSON 데이터를 Pandas DataFrame으로 정제하고, 중첩된 구조를 효율적으로 평탄화(Flattening)하는 방법을 습득습니다. 최종적으로 groupby() 함수를 이용한 탐색적 데이터 분석(EDA)과 시각화까지 완성하는 종합적인 워크플로우를 구축했습니다.

1. 데이터 분석 환경의 기초: NumPy와 Pandas의 구조

모든 복잡한 데이터 분석은 NumPy와 Pandas라는 라이브러리 기초가 중요합니다. 특히 대규모 데이터셋을 다룰 때, 이 두 라이브러리가 제공하는 벡터화 연산 성능을 이해하는 것이 중요합니다.

1.1. NumPy의 핵심: 고성능 벡터화와 브로드캐스팅

NumPy는 Python 리스트보다 훨씬 빠른 데이터 처리를 위해 배열(Array) 구조를 사용합니다. 전날에 학습했지만, 이 속도의 비결은 벡터화(Vectorization)와 브로드캐스팅(Broadcasting)에 있습니다.

연산 방식 속도 특징 예시 (100만 개 데이터 연산 시)
파이썬 for 루프 느림 (요소별 접근 및 오버헤드 발생) 약 38.4 ms
NumPy 벡터화 압도적으로 빠름 (배열 단위 일괄 처리) 약 1.8 ms (약 20배 이상 빠름)

브로드캐스팅: 크기가 다른 배열이나 배열과 스칼라(단일 값) 간에도 연산이 가능하도록 NumPy가 자동으로 배열을 확장하여 연산해주는 기능입니다. 이는 코드를 간결하고 빠르게 만듭니다.

1.2. Pandas의 핵심 구조: Series와 DataFrame

Pandas는 NumPy 배열 위에 인덱스(Index) 레이블을 붙여 데이터 조작을 용이하게 합니다.

Series (1차원 데이터)

  • 인덱스(Index)와 값(Value)의 쌍으로 구성된 1차원 자료 구조입니다.
  • 딕셔너리를 사용하여 생성할 경우, 키(Key)가 인덱스가 됩니다.

DataFrame (2차원 데이터)

  • 행(Row)과 열(Column)로 구성된 표 형식의 데이터 구조입니다.
  • 각 열(Column)은 독립적인 Series 객체로 간주됩니다.
  • API에서 파싱한 리스트 오브 딕셔너리(List of Dictionaries) 형태의 데이터를 분석에 적합한 2차원 테이블로 변환하는 것이 DataFrame의 주요 역할입니다.

예시 코드 (DataFrame 생성):

import pandas as pd
import numpy as np

# 딕셔너리를 활용하여 DataFrame 생성 (키가 컬럼이 됨)
data = {'city': ['Seoul', 'Busan'], 'population': [9.7, 3.4]}
FRM_base = pd.DataFrame(data)

print(FRM_base)
#    city  population
# 0  Seoul         9.7
# 1  Busan         3.4

2. API 데이터 수집 및 JSON 보안 처리

현대 데이터 분석은 보통 외부 API 통신으로부터 시작됩니다. 특히 웹 서비스에서 반환되는 JSON 응답을 안전하게 처리하고 파싱하는 능력이 필수적입니다.

2.1. API 통신 개념 및 보안적 고려사항

외부 서비스에 요청을 보내고 응답을 처리하는 과정에서, 데이터의 무결성과 보안을 유지하는 것이 중요합니다.

개념 설명 보안 관점의 중요성 강조
API 키 서비스 이용 권한 인증 식별자. 절대 외부에 노출 금지. 키 유출은 데이터 탈취 및 과금 폭탄으로 이어질 수 있음.
엔드포인트 API가 특정 리소스에 접근하는 고유 URL. 허가되지 않은 엔드포인트 접근 시 정책 위반 및 서비스 거부 위험.
JSON 파싱 텍스트 응답을 Python 객체(Dict, List)로 변환. 파싱 전에 정규표현식(re)을 사용해 악성 코드나 불필요한 특수문자를 필터링하여 안정성을 높여야 함.

2.2. Python을 활용한 데이터 획득 및 파싱

urllib.request로 API 요청을 보내고, json 라이브러리의 loads() 함수를 사용하여 Raw 데이터를 Python이 이해할 수 있는 객체로 변환합니다.

예시 코드 (JSON 데이터 파싱):

import urllib.request
import json
# 예시 URL은 더미 데이터로 대체합니다.
endpoint = "접근할_API_URL" 

try:
    # 1. URL 요청 및 응답 수신
    response = urllib.request.urlopen(endpoint) 
    
    # 2. 응답 데이터 읽기 (바이너리 형태)
    raw_data = response.read() 
    
    # 3. JSON 파싱 (Python List of Dictionaries 형태로 변환)
    result = json.loads(raw_data) 
    
    print(f"파싱된 데이터의 타입: {type(result)}") 
    # 결과 예시: 파싱된 데이터의 타입: <class 'list'>

except Exception as e:
    print(f"API 통신 오류 발생: {e}")

3. 중첩된 JSON 데이터 정규화 (Flattening)

실제 API에서 받은 데이터는 분석을 어렵게 만드는 중첩(Nested) 구조를 가집니다. 이를 분석 가능한 평탄한(Flat) 2차원 DataFrame으로 변환하는 정규화 과정은 분석의 핵심입니다.

3.1. 문제 인식: 1:N 관계의 중첩 구조

예를 들어, 하나의 카트(Cart) 안에 여러 개의 제품(Product) 정보 리스트가 포함된 경우를 가정합니다. 단순한 pd.DataFrame() 생성으로는 제품별 분석이 불가능합니다.

카트 정보 (상위, 1) 제품 정보 (하위, N)
cart_id, user_id, total title, quantity

목표: 제품 하나하나가 독립적인 행(Row)을 가지도록 데이터를 펼치는 것입니다.

3.2. 핵심 로직: 이중 반복문(Nested Loop)을 활용한 평탄화

중첩된 구조를 해소하는 가장 효율적인 방법은 이중 반복문을 사용하여 상위 데이터(1)를 고정한 채, 하위 데이터(N)를 순회하며 레코드를 생성하는 것입니다.

예시 코드 (중첩 데이터 Flattening 로직):

# [가정] result는 API에서 파싱된 중첩된 카트 데이터 리스트입니다.
# result = [
#     {'id': 101, 'user_id': 1, 'products': [{'title': 'Laptop', 'quantity': 1}]},
#     {'id': 102, 'user_id': 2, 'products': [{'title': 'Mouse', 'quantity': 2}, {'title': 'Keyboard', 'quantity': 1}]},
# ]

rows = [] # 최종 DataFrame 레코드 리스트

# 1. 바깥 루프: 각 카트(Cart) 데이터를 순회하며 상위 정보 추출
for cart in result:
    cart_id = cart.get('id')
    user_id = cart.get('user_id')
    total = cart.get('total')
    
    # 2. 안쪽 루프: 해당 카트 내부의 'products' 리스트를 순회하며 하위 정보 추출
    for product in cart.get('products', []): 
        
        # 3. 평탄화된 단일 레코드(딕셔너리) 생성
        record = {
            # 상위 카트 정보 복제
            'cart_id': cart_id,
            'user_id': user_id,
            'total_purchase': total,
            
            # 하위 제품 정보
            'product_title': product.get('title'),
            'quantity': product.get('quantity') 
        }
        
        rows.append(record)

# 4. 평탄화된 데이터를 사용하여 최종 DataFrame 생성
FRM_flat = pd.DataFrame(rows)
print(FRM_flat.head())
# FRM_flat은 이제 'cart_id', 'product_title' 등 5개 컬럼을 가진 평탄한 구조가 됩니다.

4. 데이터 분석 (EDA) 및 시각화

정규화된 FRM_flat DataFrame을 기반으로 groupby()를 활용하여 유의미한 인사이트를 도출하고, 이를 시각화합니다.

4.1. groupby()를 활용한 집계 분석

groupby() 함수는 데이터를 특정 기준(컬럼)으로 그룹화하고 통계량을 계산하는 Pandas의 핵심 분석 도구입니다.

예시 코드 (사용자별 총 구매액 분석):

# 사용자 ID별 총 토탈 구매액의 합계를 계산하여 우수 고객 파악
# total_purchase 컬럼에 가격 데이터가 있다고 가정합니다.
user_total_purchase = FRM_flat.groupby('user_id')['total_purchase'].sum()

print("--- 사용자별 총 구매액 순위 (TOP 5) ---")
# 총 구매액이 높은 순서대로 정렬
print(user_total_purchase.sort_values(ascending=False).head(5))

4.2. Matplotlib을 활용한 직관적인 시각화

분석 결과를 시각화하면 텍스트 결과보다 훨씬 직관적으로 인사이트를 전달할 수 있습니다.

예시 코드 (분석 결과를 바 차트로 시각화):

import matplotlib.pyplot as plt

# 4.1에서 계산된 user_total_purchase 시리즈 사용
top_users = user_total_purchase.sort_values(ascending=False).head(5)

# 폰트 깨짐 방지 및 시각화 설정
import platform
from matplotlib import font_manager, rc
plt.rcParams['axes.unicode_minus'] = False

if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    path = "c:/Windows/Fonts/malgun.ttf"
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry~~~~') 

plt.figure(figsize=(10, 6)) # 표 사이즈 설정
plt.title('사용자별 총 구매액 (상위 5명)') 

# 바 차트(Bar Chart) 생성
top_users.plot(kind='bar', color='skyblue') 
plt.xlabel("사용자 ID")
plt.ylabel("총 구매액")
plt.xticks(rotation=0) # X축 레이블 수평으로 표시
plt.grid(axis='y', linestyle='--')
plt.show() 
plt.close()

4.3. 분석 환경 확장: 웹 대시보드 도구

정적인 시각화 결과를 넘어 동적인 웹 대시보드로 배포하고 싶다면 Streamlit과 같은 도구를 활용할 수 있습니다.

도구 용도 권장 사항
Streamlit Pandas/Matplotlib 분석 결과를 웹 앱으로 빠르게 변환. 데이터 대시보드 및 내부 보고서 공유에 탁월.

5. (심화) 개인 추가 학습: NumPy 배열 차원 조작

5.1. np.newaxis를 이용한 차원 확장

데이터 분석 중 크기가 다른 배열 간의 연산(브로드캐스팅)을 위해 배열의 차원 형태를 강제로 맞춰야 할 때가 있습니다. 이때 np.newaxis를 사용합니다.

np.newaxis는 배열에 새로운 축(차원)을 추가하여 1차원 배열을 2차원 열 벡터 또는 행 벡터로 변환하는 데 유용합니다.

예시 코드 (차원 확장):

ary = np.array([1, 2, 3, 4]) 
print(f"원본 shape: {ary.shape}") # (4,)

# 1. 열 벡터로 확장 (4행 1열)
column_vector = ary[:, np.newaxis] 
print(f"열 벡터 shape: {column_vector.shape}") # (4, 1)

# 2. 행 벡터로 확장 (1행 4열)
row_vector = ary[np.newaxis, :] 
print(f"행 벡터 shape: {row_vector.shape}")    # (1, 4)

댓글 남기기