[내배캠] 데이터분석 6기/본캠프 기록

[본캠프 20일차] 파이썬 공부, 파이썬 코드카타, SQL 코드카타

물맨두 2025. 3. 17. 22:40

 

오늘 한 일은,

  • SQL 공부
    • [코드카타] SQL 8문제 풀기 (88~95번) 3문제 풀기 (88~90번)
  • 파이썬 공부
    • [코드카타] 알고리즘 1문제 풀기 (58번)
    • [데이터 전처리 & 시각화] 2~3주차 수강하기
    • [라이브 세션] 파이썬 4회차 수강하기

 


 

파이썬 공부①: [데이터 전처리 & 시각화] 2~3주차 수강하기

주말 동안 그냥 쉬려고 했는데 금요일에 받은 맥북에도 익숙해지고 1주차는 학습 환경 세팅하는 내용인 것 같아서 일요일에 들으면서 비주얼 스튜디오도 깔고 이것저것 했다.

 

Pandas

Pandas 불러오기

#실행 시 에러가 발생하면 터미널에 다음과 같이 작성해 실행하기
#pip install pandas 
import pandas as pd

 

데이터 불러오기 / 저장하기

#csv 파일 불러오기
df = pd.read_csv('파일 경로/파일명.csv')
#엑셀 파일 불러오기
df = pd.read_excel('파일 경로/파일명.xlsx')

#csv 파일로 데이터 저장하기
#index=False는 인덱스가 없는 상태로 저장하겠다는 의미. 생략해주면 인덱스가 붙어서 저장됨.
#저장할 때 경로를 생략하고 파일명만 적어주면 지금 작성하는 ipynb 파일이 저장된 파일에 저장하겠다는 의미.
data.to_csv('파일명.csv', index=False)

 

인덱스(Index)

#기본 정수 인덱스를 가진 데이터프레임 생성
df = pd.DataFrame({'A' : [1, 2, 3], 'B' : ['a', 'b', 'c']})
#사용자가 직접 인덱스를 설정해 데이터프레임 생성
df = pd.DataFrame({
    'A' : [1, 2, 3],
    'B' : ['a', 'b', 'c']
}, index=['idx1', 'idx2', 'idx3'])

#특정 인덱스의 행에 접근
df.loc['idx2']

#인덱스를 기준으로 데이터프레임을 정렬
df.sort_index()

#df가 가지고 있는 특정 컬럼(예시는 'A')을 인덱스로 설정하기
df.set_index('A')
#파일을 불러올 때 인덱스 지정하기
pd.read_csv('./data/file.csv', index_col = '컬럼정보')
pd.read_csv('./data/file.csv', index_col = 0) #0부터 시작

#인덱스 확인하기
df.index

#리스트를 활용해 인덱스 새로 입력하기
df.index = ['1번', '2번', '3번']

 

컬럼(Column)

#실습을 위한 예시 데이터프레임 생성하기
data = {
    'name' : ['Alice', 'Bob', 'Charlie'],
    'age' : [25, 30, 35],
    'gender' : ['female', 'male', 'female']
}

df = pd.DataFrame(data)

#특정 컬럼만 조회하기
df['name']

#해당 데이터프레임의 모든 컬럼들 조회하기
df.columns

#컬럼 이름 바꿔서 조회하기
#방법1. 리스트 활용해 컬럼명 새로 입력하기
df.columns = ['이름', '나이', '성별']
#방법2. .rename() 메소드 이용하기
df = df.rename(columns={'이름' : 'Name', '나이' : 'Age', '성별' : 'Gender'})

#새로운 컬럼 추가하기
df['Sport'] = ['football', 'running', 'volleyball']

#컬럼 삭제하기
del df['Sport']

 

데이터 확인하기

#데이터의 첫 행 5건만 보기
df.head()
df.head(30) #첫 행부터 30건 보기

#데이터 타입만 확인하기
df.dtypes
#데이터의 정보를 파악하기 (인덱스, 컬럼명, 컬럼당 데이터 개수, 데이터 타입)
df.info()
#데이터의 기초통계량을 보기 (개수, 평균, 표준편차, 사분위, 중앙값)
df.describe()
#특정 컬럼의 데이터 타입 확인하기
df['total_bill'].dtype

#결측치 확인하기; 데이터프레임 내에서 결측치를 확인해 그 결과를 T/F로 반환
df.isna()
df.isnull()
#결측치 개수 확인하기
df.isnull().sum()
#특정 컬럼의 결측치 확인하기
df[df['B'].isna()]

#특정 컬럼의 데이터타입 바꾸기
df['total_bill'] = df['total_bill'].astype(str)
df['total_bill'] = df['total_bill'].astype(float).astype(info) #이렇게 연달아서도 가능

 

데이터 선택하기 (iloc와 loc, Boolean Indexing)

#iloc: 정수 기반의 인덱스를 이용해 데이터를 슬라이싱 하기
#첫 행만 보기
df.iloc[0]

#두 번째 행부터 세 번째 행까지 보기
df.iloc[1:3] #정수로 슬라이싱 할 때 stop에 적는 숫자를 포함하지 않고 n-1한 값까지만 불러옴

#첫 행부터 네 번째 행까지 격줄로 보기
df.iloc[0:5:2]

#네 번째 행의 3번째 컬럼값 보기 (=2차 함수의 (x, y) 좌표값처럼)
df.iloc[3, 2]

#첫 행의 첫 번째 컬럼부터 두 번째 컬럼값까지 보기 (=x나 y를 범위로도 줄 수 있음)
df.iloc[0, 0:2]
#loc: 레이블 기반의 인덱스를 이용해 데이터를 슬라이싱 하기
#인덱스가 번호가 아닌 특정 문자인 경우!

#'A'컬럼의 전체 행을 조회
df.loc[:, 'A']
df['A'] #데이터프레임['컬럼명'] <--으로도 동일한 내용이 조회됨

#여러 개의 컬럼을 내가 원하는 순서로 조회
df[['C', 'A', 'B']]

#'A' 컬럼의 인덱스 'b'부터 끝까지 조회
df.loc['b':, 'A']

#'A'컬럼부터 'B'컬럼까지에서 인덱스 'b'부터 'd'까지 조회
df.loc['b':'d', 'A':'B']
#Boolean Indexing: 조건에 따라 데이터 선택하기

#and(&) 조건으로 
df[(df['day']=='Fri') & (df['smoker']=='No')]

#or(|) 조건으로
df[(df['tip']>5) | (df['sex']=='Female')]

#loc 메소드와 Boolean Indexing 함께 사용하기
#컬럼 'size'가 4 초과인 건만 전체 컬럼 조회하기
df.loc[df['size']>4, :]
#컬럼 'size'가 4 초과인 건만 원하는 컬럼들 원하는 순서로 조회하기
df.loc[df['size']>4, ['sex', 'tip', 'total_bill', 'day', 'time']]

#.isin()메소드와 Boolean Indexing 함께 사용하기
#컬럼 'size'의 값이 1과 2 중에 해당하면서 컬럼 'total_bill'이 20을 초과하는 건 조회하기
df[(df['size'].isin([1, 2])) & (df['total_bill']>20)]
#컬럼 'day'의 값이 금요일, 토요일, 일요일 중에 해당하는 건만 조회하기
df[df['day'].isin(['Fri', 'Sat', 'Sun'])]
  • 조건 관련 코드 작성 시 유용한 팁 조건이 길어져 복잡해지는 경우
    • 1️⃣ 각 조건을 변수로 할당하여 작성함
    • 2️⃣ 조건이 여럿일 경우 줄바꿈 기호(\)로 각 조건마다 끊어서 작성

 

데이터 추가하기

#컬럼 'created_at'에 '2024-01-01' 값을 넣어 조회하기
df['created_at'] = '2024-01-01'
#컬럼 'created_at'의 데이터 타입 변환하기 (str -> datetime)
df['created_at'] = pd.to_datetime(df['created_at'])

#연산한 값을 컬럼으로 추가하기
#total_bill과 tip 더한 값을 컬럼 'revenue'로 추가하기
df['revenue'] = df['total_bill'] + df['tip']
#revenue와 tip를 사용해 컬럼 'tip_ratio' 구하기
df['tip_ratio'] = round(df['tip'] / df['revenue'] * 100, 2)

 

데이터 병합하기 (concat, merge)

#.concat() 메소드
#axis 조건 생략 시 세로로 병합
pd.concat([df1, df2, df3]) #기존의 인덱스를 유지한 채
pd.concat([df1, df2, df3]).reset_index(drop=True) #병합한 상태에서 새로이 인덱스를 부여
#axis=1, 가로로 병합
pd.concat([df1, df2], axis=1)

#병합하려는 데이터프레임끼리 열/행의 수가 일치하지 않아도 병합 가능. 안 맞는 부분은 NaN으로 채워짐
#.merge() 메소드
#공통 컬럼으로 병합한다는 점에서 SQL의 JOIN과 유사
#컬럼 'key'공통컬럼으로 inner join
pd.merge(df1, df2, on='key') #how 조건 생략 시 inner join
#컬럼 'key'공통컬럼으로 outer join
pd.merge(df1, df2, on='key', how='outer') #how으로 'left', 'right'도 가능

 

데이터 집계하기 (groupby, pivot)

#groupby 사용하기
#컬럼 'Category'별 평균 구하기
df.groupby('Category').mean() #mean() 외에 sum(), count() 등 가능
#컬럼 'Category'별 값을 리스트에 담아 나열하기
df.groupby('Category').agg(list)

#컬럼 'day'별 평균값을 데이터프레임 중 일부 컬럼만 추출해서 조회하기
df[['day', 'total_bill', 'tip', 'size']].groupby('day').mean()
#groupby의 기준 컬럼을 2개도 취할 수 있음
df[['sex', 'day', 'total_bill', 'tip', 'size']].groupby(['day', 'sex']).mean()
#각 컬럼별 구하고자 하는 값을 지정해 줄 수도 있음
df[['sex', 'day', 'total_bill', 'tip', 'size']].groupby(['day', 'sex']).agg({'total_bill':'max', 'tip':'mean', 'size':'sum'})
#피봇 테이블 사용하기
#index는 제목열(해당 컬럼의 값들로), columns는 제목행(해당 컬럼의 값들로), values는 계산할 값이 있는 컬럼, aggfunc는 어떤 방식으로 집계할 것인지
pivot = df.pivot_table(index='Date', columns='Category', values='Value', aggfunc='sum')
#피봇 테이블을 만들 때 columns로 2개 이상의 컬럼을 취할 수도 있음
pivot = df.pivot_table(index='Date', columns=['Category', 'SubCategory'], values='Value', aggfunc='sum')

 

데이터 정렬하기 (sort_values, sort_index)

#.sort_values 메소드 사용하기 #해당 컬럼의 값을 기준으로 정렬
#컬럼 'Age'를 기준으로 오름차순 정렬하기
df.sort_values(by='Age')
#컬럼 'Age'를 기준으로 내림차순 정렬하기
df.sort_values(by='Age', ascending=False)
#컬럼 'Age'와 'Score'를 기준으로 둘 다 내림차순 정렬하기
df.sort_values(by=['Age', 'Score'], ascending=False)
#컬럼 'Age'는 오름차순으로, 'Score'는 내림차순으로 정렬하기
df.sort_values(by=['Age', 'Score'], ascending=[True, False])

#.sort_index 메소드 사용하기 #인덱스를 기준으로 정렬
#인덱스를 기준으로 내림차순 정렬하기
df.sort_index(ascending=False)

너무.. 너무 많은 걸 한꺼번에 배웠드아.....


[숙제] seaborn의 내장 데이터셋 iris를 활용해서 전처리하기

Q1. 'species' 컬럼 값이 'setosa'인 데이터 선택하기

iris_data[iris_data['species'] == 'setosa']
#정답코드는 iris_data.loc[iris_data['species'] == 'setosa'] #근데 내 코드나 정답 코드나 결과는 같음

Q2. 10부터 20까지의 행과 1부터 3까지의 열 선택하기

iris_data.iloc[10:21, 1:4]

 

seaborn의 내장 데이터셋 tips를 활용해서 전처리하기

Q1. total_bill이 30 이상인 데이터만 선택하기

tip_data[tip_data['total_bill'] >= 30]

Q2. sex를 기준으로 데이터 그룹화하여 팁의 평균 계산하기

tip_data[['sex', 'tip']].groupby('sex').mean()
#정답 코드는 tip_data.groupby('sex')['tip'].mean() #둘 다 결과는 똑같음

(아니 근데 왜 FutherWarning을 하는 거임...)

Q3. day와 time을 기준으로 데이터 그룹화하여 total_bill의 총합 계산하기

tip_data[['total_bill', 'day', 'time']].groupby(['day', 'time']).sum()
#정답 코드는 tip_data.groupby(['day', 'time'])['total_bill'].sum() #결과는 둘 다 동일함

Q4. day 열을 기준으로 요일별로 팁의 평균을 새로운 데이터프레임으로 만든 후, 이를 기존의 tips_data에 병합하기

#요일별 팁 평균 구해서 새로운 데이터프레임 만들기
tip_df = tip_data[['day', 'tip']].groupby('day').mean()
#정답 코드는,
#avg_tip_per_day = tip_data.groupby('day')['tip'].mean().reset_index()
#avg_tip_per_day.columns = ['day', 'avg_tip'] #결과는 둘 다 동일

#기존의 데이터에 새로 만든 데이터프레임 합치기
pd.merge(tip_data, tip_df, on='day', how='left')
#이건 정답 코드와 일치해서 정답 코드 첨부 안 함

 


 

파이썬 공부②: [코드카타] 알고리즘 문제 풀기 (58번)

58. 소수 만들기

주어진 숫자 중 3개의 수를 더했을 때 소수가 되는 경우의 개수를 구하려고 합니다. 숫자들이 들어있는 배열 nums가 매개변수로 주어질 때, nums에 있는 숫자들 중 서로 다른 3개를 골라 더했을 때 소수가 되는 경우의 개수를 return 하도록 solution 함수를 완성해주세요.

 

...오늘은 파이썬 공부가 쉽지 않구만.

 

우선 파이썬에 소수를 구하는 함수가 있는지 찾아봤다.

[참고] [이것이 코딩 테스트다 with Python] 37강 소수 판별 알고리즘

import math

# 소수 판별 함수
def is_prime_number(x):
    for i in range(2, int(math.sqrt(x)) + 1): # 2부터 x의 제곱근까지의 모든 수를 확인
        if x % i == 0: # x가 해당 수로 나누어떨어지면
            return False # 소수 아님
    return True # 소수 맞음

찾아보니 소수를 구할 수 있는 함수가 있는 것이 아니라 짝수를 판별할 때처럼 소수도 판별하는 통상적인 논리가 있었다.

 

또한 숫자 3개를 중복 없이 구할 수 있는 함수가 있는지 찾아봤다.

[참고] [Python] 순열(permutations)과 조합(combinations)

from itertools import combinations

combinations(반복가능한 객체, r) #반복가능한 객체에서 r개를 선택함

#조합(combinations) : 서로 다른 n개에서 r개를 선택할 때 순서를 고려하지 않고서 중복 없이 뽑을 경우의 수
#함수 사용 후 조합에 대한 값을 보고 싶으면 list()에 담아야 함

이번엔 관련된 함수가 있었다.

 

import math
from itertools import combinations

# 소수 판별 함수 생성
def is_prime_number(x):
    for i in range(2, int(math.sqrt(x)) + 1):
        if x % i == 0: 
            return False
    return True

#숫자 3개로 소수 만들기 함수 생성
def solution(nums):
    answer = 0
    num_list = []
    for i in list(combinations(nums, 3)):
        if is_prime_number(sum(i)):
            answer += 1
    return answer

앞서 찾은 정보들을 조합해서 위와 같이 코드를 작성했다.

 


 

SQL 공부: [코드카타] SQL 문제 풀기 (88~90번)

…나 있지, 리트코드 문제들 싫어. 문제도 이상해가지고, 런타임 에러 왜 이렇게 많이 나와… 흐아

문제 사이트 바뀌면서 난이도 쉬워졌다고 한 게 언제라고 벌써 이렇게 어렵지?

88. (1280) Students and Examinations

Write a solution to find the number of times each student attended each exam.
Return the result table ordered by student_id and subject_name.

--SQL 문제 88번의 첫 번째 쿼리
SELECT e.student_id,
       s.student_name,
       e.subject_name,
       COUNT(e.subject_name) attended_exams
FROM Examinations e LEFT JOIN Students s ON e.student_id = s.student_id 
GROUP BY e.student_id, s.student_name, e.subject_name
ORDER BY e.student_id, s.student_name

해당 문제를 풀기 위해 첫 번째 작성한 쿼리는 위의 결과 테이블처럼 각 학생마다 세 과목 모두에 대한 응시 횟수가 조회되어야 하는데, 내가 작성한 SQL문은 시험을 치지 않은 과목에 대한 값을 제외된 채로 조회됐기에 틀렸다는 결과가 떴다.

 

[참고] SQL 기본 문법: JOIN(INNER, OUTER, CROSS, SELF JOIN)

그러면 대체 어떻게 테이블들을 결합해야 하는지 'SQL 조인'으로 구글링하다가 CROSS JOIN에 대해서 발견했다. 

 

Students 테이블과 Subjects 테이블을 CROSS JOIN 하는 것만으로도 이렇게 위와 같이 원하는 테이블의 형태를 만들 수 있다.

 

--SQL 문제 88번의 두 번째 쿼리
SELECT s.student_id,
       s.student_name,
       c.subject_name,
       IFNULL(COUNT(e.student_id), 0) attended_exams
FROM Students s CROSS JOIN Subjects c
     LEFT JOIN Examinations e ON (s.student_id = e.student_id) AND (c.subject_name = e.subject_name)
GROUP BY s.student_id, s.student_name, c.subject_name
ORDER BY s.student_id, s.student_name

몇 번의 런타임 에러가 뜬 후 정답으로 인정된 쿼리다.

우선 Students 테이블과 Subjects 테이블을 CROSS JOIN 한 상태에서 Examinations 테이블을 LEFT JOIN 했다. 이때 공통 컬럼으로 student_id와 subject_name 모두를 지정해줘야 한다.

그 다음부턴 문제에서 요구한 대로 구하면 되는데 attended_exams 컬럼을 집계할 때 NULL값이 있을 경우 0으로 조회되도록 작성했다.

 

89. (570) Managers with at least 5 Direct Reports

Write a solution to find managers with at least five direct reports.
Return the result table in any order.

--SQL 문제 89번의 첫 번째 쿼리
SELECT name
FROM Employee
WHERE managerId IS NULL

첫 번째로 시도한 쿼리에서 john은 managerId가 아니므로 결과 테이블에 조회돼선 안 되는데 조회돼서 틀렸다.

 

--SQL 문제 89번의 두 번째 쿼리
SELECT name
FROM Employee
WHERE id IN (
    SELECT managerId
    FROM Employee
    GROUP BY managerId
    HAVING COUNT(managerId) >= 5
)

WHERE절로 id가 managerId 중에 있을 때만 조회되도록 조건을 줬는데, 그 중 managerId별로 횟수를 집계했을 때 5 이상인 경우에 해당하는 managerId로 조건을 정확하게 줬다.