```{r}
# 필요한 라이브러리 로드
library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)
library(stringr)
library(lubridate)
library(tidyverse)
# 필요한 데이터 로드, 저장 및 확인
# 파일 경로는 기기에 따라 수정 필요
file_path <- "C:\\Users\\오권영\\Desktop\\2501\\문데분\\simple.csv"
ytb_df <- read.csv(file_path, fileEncoding = "UTF-8")
head(df)
nrow(df)
#전처리
#1)결측치 확인
# 진짜 NA + 빈 문자열 + "NA"라는 문자열까지 감지
sapply(ytb_df, function(x) sum(is.na(x) | x == "" | x == "NA"))
#description :497, video_tags =4669, language=2780
#2)결측지 제거
# video_tags 결측값 → "기타"로 처리
ytb_df$video_tags[is.na(ytb_df$video_tags)] <- "기타"
ytb_df <- na.omit(ytb_df)
nrow(ytb_df)
#정상 처리 확인
colSums(is.na(ytb_df[c("video_tags","video_category_id")]))
#3)날짜 정제->Date타입으로
ytb_df$video_published <- as.Date(ytb_df$video_published_at)
#4)날짜별 정렬
ytb_df <- ytb_df %>%
arrange(video_published)
# #오채원
```r
(오채원)
```{r}
# 1) 여러 번 등장한 채널
ytb_df %>%
group_by(channel_name) %>%
summarise(count = n()) %>%
arrange(desc(count))
# 태그 리스트로 나누기
ytb_df$tags <- strsplit(ytb_df$video_tags, ",")
# 2) 전체 태그 순위 확인(중복 영상 포함함)
ytb_df %>%
select(tags) %>%
unnest(tags) %>%
filter(!is.na(tags)) %>%
group_by(tags) %>%
summarise(count = n(), .groups = "drop") %>%
arrange(desc(count))
# 3) 중복 영상 제외 태그 순위
ytb_df %>%
select(video_id, tags) %>%
unnest(tags) %>%
filter(!is.na(tags)) %>%
distinct(video_id, tags) %>%
group_by(tags) %>%
summarise(count = n(), .groups = "drop") %>%
arrange(desc(count))
# 4) 가장 오래 인급동을 유지한...
ytb_df %>%
select(video_title, trending_span) %>%
arrange(desc(trending_span))
# 5) 좋아요 / 조회수 살펴보기
ytb_df %>%
mutate(lpv = avg_log_likes / avg_log_views) %>%
group_by(video_id) %>%
slice_max(lpv, n = 1, with_ties = FALSE) %>% # lpv가 가장 큰큰
ungroup() %>%
select(video_title, channel_name, lpv) %>%
arrange(desc(lpv))
# 대부분이 아이돌, kpop 관련 => 충성도 높음
# 6) 좋아요 - 댓글수 많은... (논란 여지 있는?)
ytb_df %>%
mutate(lmc = avg_log_likes - avg_log_comments) %>%
group_by(video_id) %>%
slice_min(lmc, n = 1, with_ties = FALSE) %>% # lmc가 가장 작은 한 행만(차이가 가장 크다는...)
ungroup() %>%
select(video_title, channel_name, lmc) %>%
arrange(lmc)
# 7) 월별 인기 동영상
ytb_df %>%
mutate(
month = format(as.Date(video_published), "%Y-%m") # 월 단위로 변환
) %>%
filter(month >= "2023-11" & month <= "2025-05") %>%
group_by(month, video_id, video_title) %>%
summarise(count = n(), .groups = "drop") %>%
group_by(month) %>%
slice_max(count, n = 5, with_ties = FALSE) %>%
select(month, video_title, count) %>%
arrange(month, desc(count))
# 8) 7번의 시각화
# 1. 전처리: 월별 top 10 태그 계산
top_tags <- ytb_df %>%
mutate(month = format(as.Date(video_published), "%Y-%m")) %>%
filter(month >= "2023-11" & month <= "2025-05") %>%
select(month, tags) %>%
unnest(tags) %>%
filter(!is.na(tags)) %>%
group_by(month, tags) %>%
summarise(count = n(), .groups = "drop") %>%
group_by(month) %>%
slice_max(count, n = 10, with_ties = FALSE)
# 2. 월별로 하나씩 그래프 출력
months <- unique(top_tags$month)
for (m in months) {
cat("\n\n📊", m, "\n") # 월 출력
p <- top_tags %>%
filter(month == m) %>%
ggplot(aes(x = reorder(tags, count), y = count)) +
geom_col(fill = "yellowgreen") +
coord_flip() +
labs(title = paste("Top 10 Tags in", m), x = "Tag", y = "Count") +
theme_minimal()
print(p) # 그래프 출력
Sys.sleep(1.5) # 잠시 멈추기 (옵션) — 그래프가 보이게 하려면 IDE에 따라 필요하다네염
}
library(tidyverse)
# 전처리: 월별 top 10 태그 계산
top_tags <- ytb_df %>%
mutate(month = format(as.Date(video_published), "%Y-%m")) %>%
filter(month >= "2023-11" & month <= "2025-05") %>%
select(month, tags) %>%
unnest(tags) %>%
filter(!is.na(tags)) %>%
group_by(month, tags) %>%
summarise(count = n(), .groups = "drop") %>%
group_by(month) %>%
slice_max(count, n = 10, with_ties = FALSE)
# 월 목록 정렬
months <- sort(unique(top_tags$month))
# 월별 그래프 출력
for (m in months) {
cat("\n\n📊", m, "\n")
plot_data <- top_tags %>% filter(month == m)
p <- ggplot(plot_data, aes(x = reorder(tags, count), y = count, fill = count)) +
geom_col() +
scale_fill_gradient(
low = "yellow", # 연한 노랑
high = "yellowgreen", # 진한 노랑
guide = "none"
) +
coord_flip() +
labs(
title = paste("Top 10 Tags in", m),
x = "Tag",
y = "Count"
) +
theme_minimal(base_size = 14)
print(p)
Sys.sleep(1.5)
}
------------여기까지 EDA----------------- 인사이트 : 좋아요, 댓글 등 단순 시청 뿐만 아니라 반응도도 높은 것은 k-pop 관련! 단순히 조회수가 높은 것은 먹방, 브이로그 등임. 우리가 할 수 있는 것은 무엇?
```r
```{r}
# 태그 별 평균 좋아요
tag_likes_avg <- ytb_df %>%
select(tags, avg_log_likes) %>%
unnest(tags) %>%
filter(!is.na(tags)) %>%
group_by(tags) %>%
summarise(
total_likes = sum(avg_log_likes, na.rm = TRUE),
tag_count = n(),
avg_likes = total_likes / tag_count
) %>%
arrange(desc(avg_likes))
tag_likes_avg
tag_likes_avg %>%
slice_max(total_likes, n = 20) %>%
ggplot(aes(x = reorder(tags, avg_likes), y = avg_likes, fill = avg_likes)) +
geom_col() +
coord_flip() +
scale_fill_gradient(low = "#FFE082", high = "#FF6F00") +
labs(
title = "태그별 좋아요 합계 (상위 20개)",
x = "태그",
y = "좋아요 합계"
) +
theme_minimal(base_size = 14)
```{r}
# 1) 조회수 vs 좋아요 상관관계 시각화
ggplot(ytb_df, aes(x = avg_log_views, y = avg_log_likes)) +
geom_point(color = "black", alpha = 0.6) +
labs(title = "view vs likes", x = "view수", y = "likes") +
theme_minimal() +
scale_x_log10() +
scale_y_log10()
# 2) 조회수 vs 댓글 상관관계 시각화
ggplot(ytb_df, aes(x = avg_log_views, y = avg_log_comments)) +
geom_point(color = "skyblue", alpha = 0.6) +
labs(title = "View vs Comments", x = "View", y = "Comments") +
theme_minimal() +
scale_x_log10() +
scale_y_log10()
# 3) 조회수 vs 구독자 상관관계 시각화
ggplot(ytb_df, aes(x = avg_log_views, y = channel_subscriber_count)) +
geom_point(color = "red", alpha = 0.6) +
labs(title = "조회수 vs 구독자 수", x = "로그 조회수", y = "구독자 수") +
theme_minimal() +
scale_x_log10() +
scale_y_log10()
# 4) 조회수와 가장 관련 있는 요소는? (피어슨 상관계수)
# 변수 추출
df_corr <- ytb_df %>%
select(avg_log_views, avg_log_likes, avg_log_comments, channel_subscriber_count) %>%
na.omit()
#피어슨 상관계수 계산
corr_results <- data.frame(
variable = c("구독자 수", "좋아요 수", "댓글 수"),
pearson_r = c(
cor(df_corr$avg_log_views, df_corr$channel_subscriber_count, method = "pearson"),
cor(df_corr$avg_log_views, df_corr$avg_log_likes, method = "pearson"),
cor(df_corr$avg_log_views, df_corr$avg_log_comments, method = "pearson")
)
)
print(corr_results)
#피어슨 상관계수 막대그래프 시각화
ggplot(corr_results, aes(x = reorder(variable, pearson_r), y = pearson_r, fill = variable)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(pearson_r, 3)), vjust = -0.5, size = 5) +
labs(title = "조회수와의 피어슨 상관계수",
x = "변수", y = "피어슨 상관계수") +
ylim(0, 1) +
theme_minimal() +
theme(legend.position = "none",
text = element_text(size = 14))
# 적은 구독자 대비 높은 좋아유
#구독자 2만 이하 좋아요 수 내림차순
df_20k <- ytb_df %>%
filter(channel_subscriber_count <= 20000) %>%
arrange(desc(avg_log_likes)) %>%
distinct(video_id, .keep_all = TRUE)
#구독자 5만 이하 좋아요 수 내림차순
df_50k <- ytb_df %>%
filter(channel_subscriber_count <= 50000) %>%
arrange(desc(avg_log_likes)) %>%
distinct(video_id, .keep_all = TRUE)
head(df_20k[, c("video_title", "channel_subscriber_count", "avg_log_likes")])
head(df_50k[, c("video_title", "channel_subscriber_count", "avg_log_likes")])
```{r}
# 1) 동영상 길이와 인기 지속 기간의 상관관계
# 'PT34M29S' 형식을 초 단위로 변환하는 함수 정의
duration_to_seconds <- function(duration) {
# 정규식을 이용해 분(M)과 초(S) 추출
mins <- str_extract(duration, "(?<=PT)\\d+(?=M)")
secs <- str_extract(duration, "(?<=M)\\d+(?=S)")
# 결측값 간처리: M이나 S가 없으면 0으로 처리
mins_num <- as.numeric(ifelse(is.na(mins), 0, mins))
secs_num <- as.numeric(ifelse(is.na(secs), 0, secs))
# 분 * 60 + 초 계산
return(mins_num * 60 + secs_num)
}
# video_duration 컬럼을 초 단위로 변환
ytb_df$duration_sec <- sapply(ytb_df$video_duration, duration_to_seconds)
# 분 단위 변환
ytb_df$duration_min <- ytb_df$duration_sec / 60
# 변환 후 확인
head(ytb_df[, c("video_duration", "duration_min")])
# 동영상 길이와 인기 지속 기간 사이 상관관계 산점도
ggplot(ytb_df, aes(x = duration_min, y = trending_span)) +
geom_point(color = "blue", alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE, color = "red") + # 선형 회귀선
labs(title = "동영상 길이와 인기 지속 기간의 상관관계",
x = "동영상 길이 (분)",
y = "인기 지속 기간 (일)") +
theme_minimal()
# 선형 회귀 모델 만들기
model <- lm(trending_span ~ duration_min, data = ytb_df)
# 회귀 모델 요약 출력
summary(model)
# 2) 게시 시간대와 영상의 인기
# 인기 지속 기간이 높은 상위 100개 영상 선택
top_100_videos <- ytb_df %>%
arrange(desc(trending_span)) %>% # 인기 지속 기간 기준 내림차순 정렬
head(100) # 상위 100개 선택
# 업로드 시점을 시각화
ggplot(top_100_videos, aes(x = as.POSIXct(video_published_at, format = "%Y-%m-%d %H:%M:%S"))) +
geom_histogram(binwidth = 3600, fill = "steelblue", color = "black") + # 1시간 단위로 binning
labs(title = "인기 지속 기간이 높은 100개 영상의 업로드 시점",
x = "업로드 시점", y = "업로드 빈도 (1시간 단위)") +
theme_minimal()
top_100_videos <- top_100_videos %>%
mutate(publish_hour = hour(as.POSIXct(video_published_at, format = "%Y-%m-%d %H:%M:%S"))) # 게시 시간대 추출
ggplot(top_100_videos, aes(x = as.factor(publish_hour))) +
geom_bar(fill = "lightgreen") +
labs(title = "상위 100개 영상의 게시 시간대 분포",
x = "게시 시간대 (시)", y = "영상 수") +
theme_minimal()
# 3) 인기 영상들의 영상 길이
# 인기 지속 기간이 높은 상위 100개 영상 선택
top_100_videos <- ytb_df %>%
arrange(desc(trending_span)) %>% # 인기 지속 기간 기준 내림차순 정렬
head(100) # 상위 100개 선택
# 'video_duration'을 초 단위로 변환하는 함수 정의
duration_to_seconds <- function(duration) {
mins <- str_extract(duration, "(?<=PT)\\d+(?=M)")
secs <- str_extract(duration, "(?<=M)\\d+(?=S)")
mins_num <- as.numeric(ifelse(is.na(mins), 0, mins))
secs_num <- as.numeric(ifelse(is.na(secs), 0, secs))
return(mins_num * 60 + secs_num)
}
# video_duration 컬럼을 초 단위로 변환
top_100_videos$duration_sec <- sapply(top_100_videos$video_duration, duration_to_seconds)
# 초 단위로 변환된 동영상 길이를 분 단위로 변환
ytb_df$duration_min <- ytb_df$duration_sec / 60
# 영상 길이를 나타내는 히스토그램 그리기
ggplot(top_100_videos, aes(x = duration_min)) +
geom_histogram(binwidth = 5, fill = "steelblue", color = "black") + # 1분 단위로 binning
labs(title = "인기 지속 기간 상위 100개 영상의 영상 길이",
x = "영상 길이 (분)",
y = "영상 수") +
theme_minimal()
ggplot(top_100_videos, aes(x = "", y = duration_min)) +
geom_boxplot(fill = "lightgreen") +
labs(title = "인기 지속 기간 상위 100개 영상의 영상 길이 분포",
x = "", y = "영상 길이 (분)") +
theme_minimal()