From 8bc38cc8a85abf146e16db8ad2edb302c0fee5bb Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 21:46:51 +0200 Subject: [PATCH 01/26] test push --- category/ytb_data.py | 214 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 category/ytb_data.py diff --git a/category/ytb_data.py b/category/ytb_data.py new file mode 100644 index 00000000..8fbbfbd5 --- /dev/null +++ b/category/ytb_data.py @@ -0,0 +1,214 @@ +# import required packages +import dash +from dash import dcc +from dash import html +import dash_bootstrap_components as dbc +import plotly.graph_objs as go +import numpy as np +import pandas as pd +from datetime import datetime + + +# define figure creation function +def create_figure(result): + dates = divide_dates("2020-08-11", "2022-03-22", 10) + + # make list of continents + countries = result['country'].unique() + print(countries) + + domains = [ + {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, + {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, + {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, + {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, + {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, + {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, + {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, + {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, + {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, + {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, + {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, + {'x': [0.75, 1.0], 'y': [0.66, 1.0]} + ] + #countries = ["France", "Canada"] + # make figure + fig_dict = { + "data": [], + "layout": {}, + "frames": [] + } + + # fill in most of layout + fig_dict["layout"]["hovermode"] = "closest" + fig_dict["layout"]["updatemenus"] = [ + { + "buttons": [ + { + "args": [None, {"frame": {"duration": 1000, "redraw": True}, + "fromcurrent": True, "transition": {"duration": 300, + "easing": "quadratic-in-out"}}], + "label": "Play", + "method": "animate" + }, + { + "args": [[None], {"frame": {"duration": 0, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 0}}], + "label": "Pause", + "method": "animate" + } + ], + "direction": "left", + "pad": {"r": 10, "t": 87}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top" + } + ] + + sliders_dict = { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "font": {"size": 20}, + "visible": True, + "xanchor": "right" + }, + "transition": {"duration": 300, "easing": "cubic-in-out"}, + "pad": {"b": 10, "t": 50}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [] + } + + # make data + i = 0 + for country in countries: + res_filtered = filter_dates(result, dates[0], dates[1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + fig_dict["data"].append(pie) + i+=1 + + # make frames + for x in range(1,len(dates)-1): + frame = {"data": [], "name": str(dates[x])} + i = 0 + for country in countries: + res_filtered = filter_dates(result, dates[x], dates[x+1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + frame["data"].append(pie) + i+=1 + fig_dict["frames"].append(frame) + slider_step = {"args": [ + [dates[x]], + {"frame": {"duration": 300, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 300}} + ], + "label": dates[x], + "method": "animate"} + sliders_dict["steps"].append(slider_step) + + fig_dict["layout"]["sliders"] = [sliders_dict] + + fig = go.Figure(fig_dict) + return fig + +# define dataframe creation function +def create_dataframe(): + list_file = ["archive/BR_youtube_trending_data.csv", + "archive/CA_youtube_trending_data.csv", + "archive/DE_youtube_trending_data.csv", + "archive/FR_youtube_trending_data.csv", + "archive/GB_youtube_trending_data.csv", + "archive/IN_youtube_trending_data.csv", + "archive/JP_youtube_trending_data.csv", + "archive/KR_youtube_trending_data.csv", + "archive/MX_youtube_trending_data.csv", + "archive/RU_youtube_trending_data.csv", + "archive/US_youtube_trending_data.csv"] + list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russier", "US"] + + def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + +def divide_dates(start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + +def filter_dates(result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + +# call figure and dataframe functions +df = create_dataframe() +figure = create_figure(df) + + + +# page layout +app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + +app.layout = html.Div([ + + html.Div(children=[ + html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = figure)), + + ], style={'display': 'inline-block', 'vertical-align': 'top'}), + +]) + +if __name__ == "__main__": + app.run_server(debug=True, port=8056) \ No newline at end of file From 40e150eac05826a12d8750dab628a80d53dd95b4 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 21:58:52 +0200 Subject: [PATCH 02/26] [FEAT] - add button on delta.py show category --- delta.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/delta.py b/delta.py index 5815a655..a90d8718 100644 --- a/delta.py +++ b/delta.py @@ -1,9 +1,11 @@ +from unicodedata import category import dash from dash import dcc from dash import html from energies import energies from population import population from deces import deces +from category import ytb_data # external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] @@ -12,6 +14,7 @@ pop = population.WorldPopulationStats(app) nrg = energies.Energies(app) dec = deces.Deces(app) +cat = ytb_data.app.layout main_layout = html.Div([ html.Div(className = "row", @@ -26,6 +29,8 @@ html.Br(), dcc.Link(html.Button('Décès journaliers', style={'width':"100%"}), href='/deces'), html.Br(), + dcc.Link(html.Button('Trending Youtube', style={'width':"100%"}), href='/category'), + html.Br(), html.Br(), html.Br(), html.Center(html.A('Code source', href='https://github.com/oricou/delta')), @@ -66,6 +71,8 @@ def display_page(pathname): return pop.main_layout elif pathname == '/deces': return dec.main_layout + elif pathname == '/category': + return cat else: return home_page From d604ad23bb091c5de9d978c035e03c0eea2438cb Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 22:03:09 +0200 Subject: [PATCH 03/26] [FEAT] - README to read data --- category/README.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 category/README.txt diff --git a/category/README.txt b/category/README.txt new file mode 100644 index 00000000..8f93d2cf --- /dev/null +++ b/category/README.txt @@ -0,0 +1,22 @@ +The dataset that was required for this category was not available due to a great amont of datas. + +You should be able to see by downloading the complete dataset within this link (it requires a Kaggle account thow) : +https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv +You should check if the dataset is about 800Mo. + +The ytb_data.py needs a reference to the dataset in a directory names archive/*.csv + +category +├── archive +│   ├── BR_youtube_trending_data.csv +│   ├── CA_youtube_trending_data.csv +│   ├── DE_youtube_trending_data.csv +│   ├── FR_youtube_trending_data.csv +│   ├── GB_youtube_trending_data.csv +│   ├── IN_youtube_trending_data.csv +│   ├── JP_youtube_trending_data.csv +│   ├── KR_youtube_trending_data.csv +│   ├── MX_youtube_trending_data.csv +│   ├── RU_youtube_trending_data.csv +│   └── US_youtube_trending_data.csv + From d47ef1e2aa3f47a6df491e4858e3fc8190aa0e77 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 22:19:50 +0200 Subject: [PATCH 04/26] [FIX] - change file name --- category/ytb_data.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index 8fbbfbd5..2879c1be 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -122,17 +122,17 @@ def create_figure(result): # define dataframe creation function def create_dataframe(): - list_file = ["archive/BR_youtube_trending_data.csv", - "archive/CA_youtube_trending_data.csv", - "archive/DE_youtube_trending_data.csv", - "archive/FR_youtube_trending_data.csv", - "archive/GB_youtube_trending_data.csv", - "archive/IN_youtube_trending_data.csv", - "archive/JP_youtube_trending_data.csv", - "archive/KR_youtube_trending_data.csv", - "archive/MX_youtube_trending_data.csv", - "archive/RU_youtube_trending_data.csv", - "archive/US_youtube_trending_data.csv"] + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] list_country = ["Brésil", "Canada", "Allemagne", "France", "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russier", "US"] From 628785d015161dbb37f70dba0d8616f7c3045743 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 22:28:58 +0200 Subject: [PATCH 05/26] [FEAT] - add image in case of pb with dataset --- category/graphe_image_trend.png | Bin 0 -> 64469 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 category/graphe_image_trend.png diff --git a/category/graphe_image_trend.png b/category/graphe_image_trend.png new file mode 100644 index 0000000000000000000000000000000000000000..afb7f5622f18b96cbe8fde2e176bed923f0838cf GIT binary patch literal 64469 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 H{N?`u&vz+K literal 0 HcmV?d00001 From cb976d97c0f90f22c9b9cfda962852cc105662dc Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sat, 7 May 2022 23:08:27 +0200 Subject: [PATCH 06/26] add class --- category/ytb_data.py | 388 +++++++++++++++++++++---------------------- 1 file changed, 194 insertions(+), 194 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index 2879c1be..779a1269 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -8,207 +8,207 @@ import pandas as pd from datetime import datetime +class YoutubeTrendsStats(): + def __init__(self, application = None): + self.df = self.create_dataframe() + self.figure = self.create_figure(self.df) + + # page layout + self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + + self.app.layout = html.Div([ + + html.Div(children=[ + html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = self.figure)), + + ], style={'display': 'inline-block', 'vertical-align': 'top'}), + + ]) + + # define figure creation function + def create_figure(self,result): + dates = self.divide_dates("2020-08-11", "2022-03-22", 10) + + # make list of continents + countries = result['country'].unique() + print(countries) + + domains = [ + {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, + {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, + {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, + {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, + {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, + {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, + {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, + {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, + {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, + {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, + {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, + {'x': [0.75, 1.0], 'y': [0.66, 1.0]} + ] + #countries = ["France", "Canada"] + # make figure + fig_dict = { + "data": [], + "layout": {}, + "frames": [] + } -# define figure creation function -def create_figure(result): - dates = divide_dates("2020-08-11", "2022-03-22", 10) - - # make list of continents - countries = result['country'].unique() - print(countries) - - domains = [ - {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, - {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, - {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, - {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, - {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, - {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, - {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, - {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, - {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, - {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, - {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, - {'x': [0.75, 1.0], 'y': [0.66, 1.0]} - ] - #countries = ["France", "Canada"] - # make figure - fig_dict = { - "data": [], - "layout": {}, - "frames": [] - } - - # fill in most of layout - fig_dict["layout"]["hovermode"] = "closest" - fig_dict["layout"]["updatemenus"] = [ - { - "buttons": [ - { - "args": [None, {"frame": {"duration": 1000, "redraw": True}, - "fromcurrent": True, "transition": {"duration": 300, - "easing": "quadratic-in-out"}}], - "label": "Play", - "method": "animate" - }, - { - "args": [[None], {"frame": {"duration": 0, "redraw": True}, - "mode": "immediate", - "transition": {"duration": 0}}], - "label": "Pause", - "method": "animate" - } - ], - "direction": "left", - "pad": {"r": 10, "t": 87}, - "showactive": False, - "type": "buttons", + # fill in most of layout + fig_dict["layout"]["hovermode"] = "closest" + fig_dict["layout"]["updatemenus"] = [ + { + "buttons": [ + { + "args": [None, {"frame": {"duration": 1000, "redraw": True}, + "fromcurrent": True, "transition": {"duration": 300, + "easing": "quadratic-in-out"}}], + "label": "Play", + "method": "animate" + }, + { + "args": [[None], {"frame": {"duration": 0, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 0}}], + "label": "Pause", + "method": "animate" + } + ], + "direction": "left", + "pad": {"r": 10, "t": 87}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top" + } + ] + + sliders_dict = { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "font": {"size": 20}, + "visible": True, + "xanchor": "right" + }, + "transition": {"duration": 300, "easing": "cubic-in-out"}, + "pad": {"b": 10, "t": 50}, + "len": 0.9, "x": 0.1, - "xanchor": "right", "y": 0, - "yanchor": "top" + "steps": [] } - ] - - sliders_dict = { - "active": 0, - "yanchor": "top", - "xanchor": "left", - "currentvalue": { - "font": {"size": 20}, - "visible": True, - "xanchor": "right" - }, - "transition": {"duration": 300, "easing": "cubic-in-out"}, - "pad": {"b": 10, "t": 50}, - "len": 0.9, - "x": 0.1, - "y": 0, - "steps": [] - } - - # make data - i = 0 - for country in countries: - res_filtered = filter_dates(result, dates[0], dates[1]) - pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) - fig_dict["data"].append(pie) - i+=1 - - # make frames - for x in range(1,len(dates)-1): - frame = {"data": [], "name": str(dates[x])} + + # make data i = 0 for country in countries: - res_filtered = filter_dates(result, dates[x], dates[x+1]) + res_filtered = self.filter_dates(result, dates[0], dates[1]) pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) - frame["data"].append(pie) + fig_dict["data"].append(pie) i+=1 - fig_dict["frames"].append(frame) - slider_step = {"args": [ - [dates[x]], - {"frame": {"duration": 300, "redraw": True}, - "mode": "immediate", - "transition": {"duration": 300}} - ], - "label": dates[x], - "method": "animate"} - sliders_dict["steps"].append(slider_step) - - fig_dict["layout"]["sliders"] = [sliders_dict] - - fig = go.Figure(fig_dict) - return fig - -# define dataframe creation function -def create_dataframe(): - list_file = ["category/archive/BR_youtube_trending_data.csv", - "category/archive/CA_youtube_trending_data.csv", - "category/archive/DE_youtube_trending_data.csv", - "category/archive/FR_youtube_trending_data.csv", - "category/archive/GB_youtube_trending_data.csv", - "category/archive/IN_youtube_trending_data.csv", - "category/archive/JP_youtube_trending_data.csv", - "category/archive/KR_youtube_trending_data.csv", - "category/archive/MX_youtube_trending_data.csv", - "category/archive/RU_youtube_trending_data.csv", - "category/archive/US_youtube_trending_data.csv"] - list_country = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russier", "US"] - - def load_data(list_file, list_country): - datas = pd.DataFrame() - for i in range(len(list_file)): - data = pd.read_csv(list_file[i], sep=',') - data['country'] = list_country[i] - datas = pd.concat([datas, data]) - return datas - - data = load_data(list_file, list_country) - data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) - - result = data.copy() - result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') - replace_categories = { 2: 'Autos & Vehicles', - 1: 'Film & Animation', - 10: 'Music', - 15: 'Pets & Animals', - 17: 'Sports', - 18: 'Short Movies', - 19: 'Travel & Events', - 20: 'Gaming', - 21: 'Videoblogging', - 22: 'People & Blogs', - 23: 'Comedy', - 24: 'Entertainment', - 25: 'News & Politics', - 26: 'Howto & Style', - 27: 'Education', - 28: 'Science & Technology', - 29: 'Nonprofits & Activism'} - - df = result.replace({"categoryId": replace_categories}) - - return df - -def divide_dates(start, end, N): - test_date1 = datetime.strptime(start, '%Y-%m-%d') - test_date2 = datetime.strptime(end, '%Y-%m-%d') - temp = [] - diff = ( test_date2 - test_date1) // N - for idx in range(0, N+1): - temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) - # format - return temp - -def filter_dates(result, start_date, end_date): - after_start_date = result["trending_date"] >= start_date - before_end_date = result["trending_date"] <= end_date - between_two_dates = after_start_date & before_end_date - filtered_dates = result.loc[between_two_dates] - return filtered_dates - - -# call figure and dataframe functions -df = create_dataframe() -figure = create_figure(df) - - - -# page layout -app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) - -app.layout = html.Div([ - - html.Div(children=[ - html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), - - html.Div(dcc.Graph(id = 'main-graph', - figure = figure)), - - ], style={'display': 'inline-block', 'vertical-align': 'top'}), - -]) + + # make frames + for x in range(1,len(dates)-1): + frame = {"data": [], "name": str(dates[x])} + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[x], dates[x+1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + frame["data"].append(pie) + i+=1 + fig_dict["frames"].append(frame) + slider_step = {"args": [ + [dates[x]], + {"frame": {"duration": 300, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 300}} + ], + "label": dates[x], + "method": "animate"} + sliders_dict["steps"].append(slider_step) + + fig_dict["layout"]["sliders"] = [sliders_dict] + + fig = go.Figure(fig_dict) + return fig + + def create_dataframe(self): + list_file = ["archive/BR_youtube_trending_data.csv", + "archive/CA_youtube_trending_data.csv", + "archive/DE_youtube_trending_data.csv", + "archive/FR_youtube_trending_data.csv", + "archive/GB_youtube_trending_data.csv", + "archive/IN_youtube_trending_data.csv", + "archive/JP_youtube_trending_data.csv", + "archive/KR_youtube_trending_data.csv", + "archive/MX_youtube_trending_data.csv", + "archive/RU_youtube_trending_data.csv", + "archive/US_youtube_trending_data.csv"] + list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] + + def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + + def divide_dates(self, start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + + def filter_dates(self, result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + def run(self, debug=False, port=8050): + self.app.run_server(host="0.0.0.0", debug=debug, port=port) if __name__ == "__main__": - app.run_server(debug=True, port=8056) \ No newline at end of file + yt = YoutubeTrendsStats() + yt.run(port=8055) \ No newline at end of file From 84b35708b13be24083d9d7934c843bb12a7a112a Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 23:37:30 +0200 Subject: [PATCH 07/26] [FIX] - delta should be able to acess with button --- category/ytb_data.py | 33 ++++++++++++++++++++++----------- delta.py | 4 ++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index 779a1269..319b44ab 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -27,6 +27,17 @@ def __init__(self, application = None): ], style={'display': 'inline-block', 'vertical-align': 'top'}), ]) + self.main_layout = html.Div([ + + html.Div(children=[ + html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = self.figure)), + + ], style={'display': 'inline-block', 'vertical-align': 'top'}), + + ]) # define figure creation function def create_figure(self,result): @@ -140,17 +151,17 @@ def create_figure(self,result): return fig def create_dataframe(self): - list_file = ["archive/BR_youtube_trending_data.csv", - "archive/CA_youtube_trending_data.csv", - "archive/DE_youtube_trending_data.csv", - "archive/FR_youtube_trending_data.csv", - "archive/GB_youtube_trending_data.csv", - "archive/IN_youtube_trending_data.csv", - "archive/JP_youtube_trending_data.csv", - "archive/KR_youtube_trending_data.csv", - "archive/MX_youtube_trending_data.csv", - "archive/RU_youtube_trending_data.csv", - "archive/US_youtube_trending_data.csv"] + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] list_country = ["Brésil", "Canada", "Allemagne", "France", "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] diff --git a/delta.py b/delta.py index a90d8718..a8120f1a 100644 --- a/delta.py +++ b/delta.py @@ -14,7 +14,7 @@ pop = population.WorldPopulationStats(app) nrg = energies.Energies(app) dec = deces.Deces(app) -cat = ytb_data.app.layout +cat = ytb_data.YoutubeTrendsStats(app) main_layout = html.Div([ html.Div(className = "row", @@ -72,7 +72,7 @@ def display_page(pathname): elif pathname == '/deces': return dec.main_layout elif pathname == '/category': - return cat + return cat.main_layout else: return home_page From 36864036508a02a1d5687667dcb8608e3df29092 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sat, 7 May 2022 23:56:16 +0200 Subject: [PATCH 08/26] [FEAT] - music trend evaluation --- music/music_trend.py | 185 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 music/music_trend.py diff --git a/music/music_trend.py b/music/music_trend.py new file mode 100644 index 00000000..df703f1a --- /dev/null +++ b/music/music_trend.py @@ -0,0 +1,185 @@ +# import required packages +import dash +from dash import dcc +from dash import html +import dash_bootstrap_components as dbc +import plotly.graph_objs as go +import numpy as np +import pandas as pd +from datetime import datetime +import plotly.express as px + +d1 = "\u3131" +f1 = "\u3163" + +d2 = "\uAC00" +f2 = "\uAF00" + +d3 = "\uB000" +f3 = "\uBFE1" + +d4 = "\uC058" +f4 = "\uCFFC" + +d5 = "\uD018" +f5 = "\uD79D" + +d6 = "\u3181" +f6 = "\uCB4C" + + +list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] +list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russier", "US"] +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Méxique", "Russier", "US"] + +def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + +def is_korean(word): + l = [] + for j in word: + for i in j: + if ord(i) >= ord(d1) and ord(i) <= ord(f1): + l.append(j) + if ord(i) >= ord(d2) and ord(i) <= ord(f2): + l.append(j) + if ord(i) >= ord(d3) and ord(i) <= ord(f3): + l.append(j) + if ord(i) >= ord(d4) and ord(i) <= ord(f4): + l.append(j) + if ord(i) >= ord(d5) and ord(i) <= ord(f5): + l.append(j) + return l + +def ratio(list_country_kor, datas_year): + ratio = [] + for country in list_country_kor: + country_data = datas_year[datas_year.country == country] + country_music = country_data[country_data.categoryId == 'Music'] + country_korean = is_korean(country_music.title) + ratio.append(len(country_korean) * 100 / len(country_music)) + return ratio + +# define figure creation function +def create_figure(result, list_country_kor): + datas = result + datas.trending_date = pd.to_datetime(datas.trending_date) + datas['year'] = pd.DatetimeIndex(datas['trending_date']).year + + datas_2020 = datas[datas.year == 2020] + datas_2021 = datas[datas.year == 2021] + datas_2022 = datas[datas.year == 2022] + + ratio_2020 = ratio(list_country_kor, datas_2020) + ratio_2021 = ratio(list_country_kor, datas_2021) + ratio_2022 = ratio(list_country_kor, datas_2022) + + df = pd.DataFrame(index = list_country_kor) + df['2020'] = ratio_2020 + df['2021'] = ratio_2021 + df['2022'] = ratio_2022 + + fig = px.scatter(df, size=df*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + opacity = 0.6, labels={ + "value": "Ratio in %", + "index": "Country", + "variable": "Year" + }) + fig.update_layout( + title_font_size=22, + font_family="Serif", + title_font_family="Times New Roman", + title_font_color="black" + ) + fig.update_xaxes(title_font_family="Serif") + return fig + +# define dataframe creation function +def create_dataframe(): + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + +def divide_dates(start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + +def filter_dates(result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + +# call figure and dataframe functions +df = create_dataframe() +figure = create_figure(df, list_country_kor) + + + +# page layout +app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + +app.layout = html.Div([ + + html.Div(children=[ + html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = figure)), + + ], style={'display': 'inline-block', 'vertical-align': 'top'}), + +]) + +if __name__ == "__main__": + app.run_server(debug=True, port=8057) \ No newline at end of file From ac2283bba6c275329b059d1afeb13b6a870ce0fa Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sun, 8 May 2022 00:03:09 +0200 Subject: [PATCH 09/26] [FEAT] - add image in case data error --- music/graphe_image_music.png | Bin 0 -> 51172 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 music/graphe_image_music.png diff --git a/music/graphe_image_music.png b/music/graphe_image_music.png new file mode 100644 index 0000000000000000000000000000000000000000..2b15ecaee605ba6d9f4a44cc34369a8214999d1d GIT binary patch literal 51172 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 Date: Sun, 8 May 2022 13:53:27 +0200 Subject: [PATCH 10/26] [FIX] - Add second graph on display --- category/ytb_data.py | 106 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index 319b44ab..cb900ea1 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -7,11 +7,69 @@ import numpy as np import pandas as pd from datetime import datetime +import plotly.express as px + +def ratio(list_country_kor, datas_year): + ratio = [] + for country in list_country_kor: + country_data = datas_year[datas_year.country == country] + country_music = country_data[country_data.categoryId == 'Music'] + country_korean = is_korean(country_music.title) + ratio.append(len(country_korean) * 100 / len(country_music)) + return ratio + +list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-uni", "Inde", "Japon", "Méxique", "Russie", "US"] + +d1 = "\u3131" +f1 = "\u3163" + +d2 = "\uAC00" +f2 = "\uAF00" + +d3 = "\uB000" +f3 = "\uBFE1" + +d4 = "\uC058" +f4 = "\uCFFC" + +d5 = "\uD018" +f5 = "\uD79D" + +d6 = "\u3181" +f6 = "\uCB4C" + +def is_korean(word): + l = [] + for j in word: + for i in j: + if ord(i) >= ord(d1) and ord(i) <= ord(f1): + l.append(j) + if ord(i) >= ord(d2) and ord(i) <= ord(f2): + l.append(j) + if ord(i) >= ord(d3) and ord(i) <= ord(f3): + l.append(j) + if ord(i) >= ord(d4) and ord(i) <= ord(f4): + l.append(j) + if ord(i) >= ord(d5) and ord(i) <= ord(f5): + l.append(j) + return l + +def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas class YoutubeTrendsStats(): def __init__(self, application = None): self.df = self.create_dataframe() self.figure = self.create_figure(self.df) + self.other_figure = self.create_figure2(self.df) # page layout self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) @@ -23,6 +81,8 @@ def __init__(self, application = None): html.Div(dcc.Graph(id = 'main-graph', figure = self.figure)), + html.Div(dcc.Graph(id = 'second-graph', + figure = self.other_figure)), ], style={'display': 'inline-block', 'vertical-align': 'top'}), @@ -34,11 +94,47 @@ def __init__(self, application = None): html.Div(dcc.Graph(id = 'main-graph', figure = self.figure)), + html.Div(dcc.Graph(id = 'second-graph', + figure = self.other_figure)), ], style={'display': 'inline-block', 'vertical-align': 'top'}), ]) + def create_figure2(self, datas): + datas.trending_date = pd.to_datetime(datas.trending_date) + datas['year'] = pd.DatetimeIndex(datas['trending_date']).year + + datas_2020 = datas[datas.year == 2020] + datas_2021 = datas[datas.year == 2021] + datas_2022 = datas[datas.year == 2022] + + ratio_2020 = ratio(list_country_kor, datas_2020) + ratio_2021 = ratio(list_country_kor, datas_2021) + ratio_2022 = ratio(list_country_kor, datas_2022) + + df = pd.DataFrame(index = list_country_kor) + df['2020'] = ratio_2020 + df['2021'] = ratio_2021 + df['2022'] = ratio_2022 + + y0 = np.array(df['2020']) + fig = px.scatter(df, size=y0*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + opacity = 0.6, labels={ + "value": "Ratio in %", + "index": "Country", + "variable": "Year" + }) + fig.update_layout( + title_font_size=22, + font_family="Serif", + title_font_family="Times New Roman", + title_font_color="black" + ) + fig.update_xaxes(title_font_family="Serif") + return fig + # define figure creation function def create_figure(self,result): dates = self.divide_dates("2020-08-11", "2022-03-22", 10) @@ -162,16 +258,6 @@ def create_dataframe(self): "category/archive/MX_youtube_trending_data.csv", "category/archive/RU_youtube_trending_data.csv", "category/archive/US_youtube_trending_data.csv"] - list_country = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] - - def load_data(list_file, list_country): - datas = pd.DataFrame() - for i in range(len(list_file)): - data = pd.read_csv(list_file[i], sep=',') - data['country'] = list_country[i] - datas = pd.concat([datas, data]) - return datas data = load_data(list_file, list_country) data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) From 595c3ec87142b43abb26dbde9988a3c5a3e5ca34 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 8 May 2022 22:00:22 +0200 Subject: [PATCH 11/26] style --- category/ytb_data.py | 84 +++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index cb900ea1..96af916b 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -19,9 +19,9 @@ def ratio(list_country_kor, datas_year): return ratio list_country = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] + "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] list_country_kor = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-uni", "Inde", "Japon", "Méxique", "Russie", "US"] + "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] d1 = "\u3131" f1 = "\u3163" @@ -74,32 +74,34 @@ def __init__(self, application = None): # page layout self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) - self.app.layout = html.Div([ - - html.Div(children=[ - html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + div_content = html.Div(children=[ + # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), html.Div(dcc.Graph(id = 'main-graph', figure = self.figure)), + # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), html.Div(dcc.Graph(id = 'second-graph', figure = self.other_figure)), + html.Br(), + dcc.Markdown(""" + Le graphique est interactif. En passant la souris sur les courbes vous avez une infobulle. + + Notes : + * La catégorie Entertainement est la plus en tendance dans la plupart des pays. + * Entre le 13/04/20021 et le 01/06/2021, la catégorie Gaming représentait 13,3%/ des tendances en France + * On peut voir que la musique Coréenne représente la majorité des musiques de plusieurs pays. + - ], style={'display': 'inline-block', 'vertical-align': 'top'}), - - ]) - self.main_layout = html.Div([ + #### À propos - html.Div(children=[ - html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), - - html.Div(dcc.Graph(id = 'main-graph', - figure = self.figure)), - html.Div(dcc.Graph(id = 'second-graph', - figure = self.other_figure)), + * Sources : + * https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv + """) - ], style={'display': 'inline-block', 'vertical-align': 'top'}), + ])#, style={'display': 'inline-block', 'vertical-align': 'top'}) - ]) + self.app.layout = div_content + self.main_layout = div_content def create_figure2(self, datas): datas.trending_date = pd.to_datetime(datas.trending_date) @@ -119,25 +121,25 @@ def create_figure2(self, datas): df['2022'] = ratio_2022 y0 = np.array(df['2020']) - fig = px.scatter(df, size=y0*5, hover_name=df.index, - title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + fig = px.scatter(df, size=y0*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', opacity = 0.6, labels={ "value": "Ratio in %", "index": "Country", "variable": "Year" }) - fig.update_layout( - title_font_size=22, - font_family="Serif", - title_font_family="Times New Roman", - title_font_color="black" - ) - fig.update_xaxes(title_font_family="Serif") + #fig.update_layout( + # title_font_size=22, + # font_family="Serif", + # title_font_family="Times New Roman", + # title_font_color="black" + #) + #fig.update_xaxes(title_font_family="Serif") return fig # define figure creation function def create_figure(self,result): - dates = self.divide_dates("2020-08-11", "2022-03-22", 10) + dates = self.divide_dates("2020-08-11", "2022-03-22", 12) # make list of continents countries = result['country'].unique() @@ -166,6 +168,8 @@ def create_figure(self,result): } # fill in most of layout + fig_dict["layout"]["title"] = "Évolution des proportions des catégories youtube en tendances dans le monde" + fig_dict["layout"]["height"] = 700 fig_dict["layout"]["hovermode"] = "closest" fig_dict["layout"]["updatemenus"] = [ { @@ -247,17 +251,17 @@ def create_figure(self,result): return fig def create_dataframe(self): - list_file = ["category/archive/BR_youtube_trending_data.csv", - "category/archive/CA_youtube_trending_data.csv", - "category/archive/DE_youtube_trending_data.csv", - "category/archive/FR_youtube_trending_data.csv", - "category/archive/GB_youtube_trending_data.csv", - "category/archive/IN_youtube_trending_data.csv", - "category/archive/JP_youtube_trending_data.csv", - "category/archive/KR_youtube_trending_data.csv", - "category/archive/MX_youtube_trending_data.csv", - "category/archive/RU_youtube_trending_data.csv", - "category/archive/US_youtube_trending_data.csv"] + list_file = ["archive/BR_youtube_trending_data.csv", + "archive/CA_youtube_trending_data.csv", + "archive/DE_youtube_trending_data.csv", + "archive/FR_youtube_trending_data.csv", + "archive/GB_youtube_trending_data.csv", + "archive/IN_youtube_trending_data.csv", + "archive/JP_youtube_trending_data.csv", + "archive/KR_youtube_trending_data.csv", + "archive/MX_youtube_trending_data.csv", + "archive/RU_youtube_trending_data.csv", + "archive/US_youtube_trending_data.csv"] data = load_data(list_file, list_country) data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) From 33755e3d9ef6701e7f6b07cf18253934a465ec4f Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Sun, 8 May 2022 23:16:36 +0200 Subject: [PATCH 12/26] [FIX] - set database in archive catergory --- category/ytb_data.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/category/ytb_data.py b/category/ytb_data.py index 96af916b..37390076 100644 --- a/category/ytb_data.py +++ b/category/ytb_data.py @@ -251,17 +251,17 @@ def create_figure(self,result): return fig def create_dataframe(self): - list_file = ["archive/BR_youtube_trending_data.csv", - "archive/CA_youtube_trending_data.csv", - "archive/DE_youtube_trending_data.csv", - "archive/FR_youtube_trending_data.csv", - "archive/GB_youtube_trending_data.csv", - "archive/IN_youtube_trending_data.csv", - "archive/JP_youtube_trending_data.csv", - "archive/KR_youtube_trending_data.csv", - "archive/MX_youtube_trending_data.csv", - "archive/RU_youtube_trending_data.csv", - "archive/US_youtube_trending_data.csv"] + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] data = load_data(list_file, list_country) data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) From 3e2f7068b076e892dd9afc77e1e8e65b46c250b9 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Mon, 9 May 2022 15:08:27 +0200 Subject: [PATCH 13/26] add with name --- .../category/ytb_data.py | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 caroline_devaux_mathieu_schlinger_trending_youtube/category/ytb_data.py diff --git a/caroline_devaux_mathieu_schlinger_trending_youtube/category/ytb_data.py b/caroline_devaux_mathieu_schlinger_trending_youtube/category/ytb_data.py new file mode 100644 index 00000000..37390076 --- /dev/null +++ b/caroline_devaux_mathieu_schlinger_trending_youtube/category/ytb_data.py @@ -0,0 +1,315 @@ +# import required packages +import dash +from dash import dcc +from dash import html +import dash_bootstrap_components as dbc +import plotly.graph_objs as go +import numpy as np +import pandas as pd +from datetime import datetime +import plotly.express as px + +def ratio(list_country_kor, datas_year): + ratio = [] + for country in list_country_kor: + country_data = datas_year[datas_year.country == country] + country_music = country_data[country_data.categoryId == 'Music'] + country_korean = is_korean(country_music.title) + ratio.append(len(country_korean) * 100 / len(country_music)) + return ratio + +list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] + +d1 = "\u3131" +f1 = "\u3163" + +d2 = "\uAC00" +f2 = "\uAF00" + +d3 = "\uB000" +f3 = "\uBFE1" + +d4 = "\uC058" +f4 = "\uCFFC" + +d5 = "\uD018" +f5 = "\uD79D" + +d6 = "\u3181" +f6 = "\uCB4C" + +def is_korean(word): + l = [] + for j in word: + for i in j: + if ord(i) >= ord(d1) and ord(i) <= ord(f1): + l.append(j) + if ord(i) >= ord(d2) and ord(i) <= ord(f2): + l.append(j) + if ord(i) >= ord(d3) and ord(i) <= ord(f3): + l.append(j) + if ord(i) >= ord(d4) and ord(i) <= ord(f4): + l.append(j) + if ord(i) >= ord(d5) and ord(i) <= ord(f5): + l.append(j) + return l + +def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + +class YoutubeTrendsStats(): + def __init__(self, application = None): + self.df = self.create_dataframe() + self.figure = self.create_figure(self.df) + self.other_figure = self.create_figure2(self.df) + + # page layout + self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + + div_content = html.Div(children=[ + # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = self.figure)), + # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), + html.Div(dcc.Graph(id = 'second-graph', + figure = self.other_figure)), + html.Br(), + dcc.Markdown(""" + Le graphique est interactif. En passant la souris sur les courbes vous avez une infobulle. + + Notes : + * La catégorie Entertainement est la plus en tendance dans la plupart des pays. + * Entre le 13/04/20021 et le 01/06/2021, la catégorie Gaming représentait 13,3%/ des tendances en France + * On peut voir que la musique Coréenne représente la majorité des musiques de plusieurs pays. + + + #### À propos + + * Sources : + * https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv + """) + + ])#, style={'display': 'inline-block', 'vertical-align': 'top'}) + + self.app.layout = div_content + self.main_layout = div_content + + def create_figure2(self, datas): + datas.trending_date = pd.to_datetime(datas.trending_date) + datas['year'] = pd.DatetimeIndex(datas['trending_date']).year + + datas_2020 = datas[datas.year == 2020] + datas_2021 = datas[datas.year == 2021] + datas_2022 = datas[datas.year == 2022] + + ratio_2020 = ratio(list_country_kor, datas_2020) + ratio_2021 = ratio(list_country_kor, datas_2021) + ratio_2022 = ratio(list_country_kor, datas_2022) + + df = pd.DataFrame(index = list_country_kor) + df['2020'] = ratio_2020 + df['2021'] = ratio_2021 + df['2022'] = ratio_2022 + + y0 = np.array(df['2020']) + fig = px.scatter(df, size=y0*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + opacity = 0.6, labels={ + "value": "Ratio in %", + "index": "Country", + "variable": "Year" + }) + #fig.update_layout( + # title_font_size=22, + # font_family="Serif", + # title_font_family="Times New Roman", + # title_font_color="black" + #) + #fig.update_xaxes(title_font_family="Serif") + return fig + + # define figure creation function + def create_figure(self,result): + dates = self.divide_dates("2020-08-11", "2022-03-22", 12) + + # make list of continents + countries = result['country'].unique() + print(countries) + + domains = [ + {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, + {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, + {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, + {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, + {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, + {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, + {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, + {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, + {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, + {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, + {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, + {'x': [0.75, 1.0], 'y': [0.66, 1.0]} + ] + #countries = ["France", "Canada"] + # make figure + fig_dict = { + "data": [], + "layout": {}, + "frames": [] + } + + # fill in most of layout + fig_dict["layout"]["title"] = "Évolution des proportions des catégories youtube en tendances dans le monde" + fig_dict["layout"]["height"] = 700 + fig_dict["layout"]["hovermode"] = "closest" + fig_dict["layout"]["updatemenus"] = [ + { + "buttons": [ + { + "args": [None, {"frame": {"duration": 1000, "redraw": True}, + "fromcurrent": True, "transition": {"duration": 300, + "easing": "quadratic-in-out"}}], + "label": "Play", + "method": "animate" + }, + { + "args": [[None], {"frame": {"duration": 0, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 0}}], + "label": "Pause", + "method": "animate" + } + ], + "direction": "left", + "pad": {"r": 10, "t": 87}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top" + } + ] + + sliders_dict = { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "font": {"size": 20}, + "visible": True, + "xanchor": "right" + }, + "transition": {"duration": 300, "easing": "cubic-in-out"}, + "pad": {"b": 10, "t": 50}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [] + } + + # make data + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[0], dates[1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + fig_dict["data"].append(pie) + i+=1 + + # make frames + for x in range(1,len(dates)-1): + frame = {"data": [], "name": str(dates[x])} + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[x], dates[x+1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + frame["data"].append(pie) + i+=1 + fig_dict["frames"].append(frame) + slider_step = {"args": [ + [dates[x]], + {"frame": {"duration": 300, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 300}} + ], + "label": dates[x], + "method": "animate"} + sliders_dict["steps"].append(slider_step) + + fig_dict["layout"]["sliders"] = [sliders_dict] + + fig = go.Figure(fig_dict) + return fig + + def create_dataframe(self): + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + + def divide_dates(self, start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + + def filter_dates(self, result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + def run(self, debug=False, port=8050): + self.app.run_server(host="0.0.0.0", debug=debug, port=port) + +if __name__ == "__main__": + yt = YoutubeTrendsStats() + yt.run(port=8055) \ No newline at end of file From 9691a26c41cdad4d22535ebfe073ec8a9367df21 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Fri, 3 Jun 2022 10:19:03 +0200 Subject: [PATCH 14/26] [FIX] - Change repository name --- CDMS_trending_youtube/category/ytb_data.py | 315 +++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 CDMS_trending_youtube/category/ytb_data.py diff --git a/CDMS_trending_youtube/category/ytb_data.py b/CDMS_trending_youtube/category/ytb_data.py new file mode 100644 index 00000000..37390076 --- /dev/null +++ b/CDMS_trending_youtube/category/ytb_data.py @@ -0,0 +1,315 @@ +# import required packages +import dash +from dash import dcc +from dash import html +import dash_bootstrap_components as dbc +import plotly.graph_objs as go +import numpy as np +import pandas as pd +from datetime import datetime +import plotly.express as px + +def ratio(list_country_kor, datas_year): + ratio = [] + for country in list_country_kor: + country_data = datas_year[datas_year.country == country] + country_music = country_data[country_data.categoryId == 'Music'] + country_korean = is_korean(country_music.title) + ratio.append(len(country_korean) * 100 / len(country_music)) + return ratio + +list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] + +d1 = "\u3131" +f1 = "\u3163" + +d2 = "\uAC00" +f2 = "\uAF00" + +d3 = "\uB000" +f3 = "\uBFE1" + +d4 = "\uC058" +f4 = "\uCFFC" + +d5 = "\uD018" +f5 = "\uD79D" + +d6 = "\u3181" +f6 = "\uCB4C" + +def is_korean(word): + l = [] + for j in word: + for i in j: + if ord(i) >= ord(d1) and ord(i) <= ord(f1): + l.append(j) + if ord(i) >= ord(d2) and ord(i) <= ord(f2): + l.append(j) + if ord(i) >= ord(d3) and ord(i) <= ord(f3): + l.append(j) + if ord(i) >= ord(d4) and ord(i) <= ord(f4): + l.append(j) + if ord(i) >= ord(d5) and ord(i) <= ord(f5): + l.append(j) + return l + +def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + +class YoutubeTrendsStats(): + def __init__(self, application = None): + self.df = self.create_dataframe() + self.figure = self.create_figure(self.df) + self.other_figure = self.create_figure2(self.df) + + # page layout + self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + + div_content = html.Div(children=[ + # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = self.figure)), + # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), + html.Div(dcc.Graph(id = 'second-graph', + figure = self.other_figure)), + html.Br(), + dcc.Markdown(""" + Le graphique est interactif. En passant la souris sur les courbes vous avez une infobulle. + + Notes : + * La catégorie Entertainement est la plus en tendance dans la plupart des pays. + * Entre le 13/04/20021 et le 01/06/2021, la catégorie Gaming représentait 13,3%/ des tendances en France + * On peut voir que la musique Coréenne représente la majorité des musiques de plusieurs pays. + + + #### À propos + + * Sources : + * https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv + """) + + ])#, style={'display': 'inline-block', 'vertical-align': 'top'}) + + self.app.layout = div_content + self.main_layout = div_content + + def create_figure2(self, datas): + datas.trending_date = pd.to_datetime(datas.trending_date) + datas['year'] = pd.DatetimeIndex(datas['trending_date']).year + + datas_2020 = datas[datas.year == 2020] + datas_2021 = datas[datas.year == 2021] + datas_2022 = datas[datas.year == 2022] + + ratio_2020 = ratio(list_country_kor, datas_2020) + ratio_2021 = ratio(list_country_kor, datas_2021) + ratio_2022 = ratio(list_country_kor, datas_2022) + + df = pd.DataFrame(index = list_country_kor) + df['2020'] = ratio_2020 + df['2021'] = ratio_2021 + df['2022'] = ratio_2022 + + y0 = np.array(df['2020']) + fig = px.scatter(df, size=y0*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + opacity = 0.6, labels={ + "value": "Ratio in %", + "index": "Country", + "variable": "Year" + }) + #fig.update_layout( + # title_font_size=22, + # font_family="Serif", + # title_font_family="Times New Roman", + # title_font_color="black" + #) + #fig.update_xaxes(title_font_family="Serif") + return fig + + # define figure creation function + def create_figure(self,result): + dates = self.divide_dates("2020-08-11", "2022-03-22", 12) + + # make list of continents + countries = result['country'].unique() + print(countries) + + domains = [ + {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, + {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, + {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, + {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, + {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, + {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, + {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, + {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, + {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, + {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, + {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, + {'x': [0.75, 1.0], 'y': [0.66, 1.0]} + ] + #countries = ["France", "Canada"] + # make figure + fig_dict = { + "data": [], + "layout": {}, + "frames": [] + } + + # fill in most of layout + fig_dict["layout"]["title"] = "Évolution des proportions des catégories youtube en tendances dans le monde" + fig_dict["layout"]["height"] = 700 + fig_dict["layout"]["hovermode"] = "closest" + fig_dict["layout"]["updatemenus"] = [ + { + "buttons": [ + { + "args": [None, {"frame": {"duration": 1000, "redraw": True}, + "fromcurrent": True, "transition": {"duration": 300, + "easing": "quadratic-in-out"}}], + "label": "Play", + "method": "animate" + }, + { + "args": [[None], {"frame": {"duration": 0, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 0}}], + "label": "Pause", + "method": "animate" + } + ], + "direction": "left", + "pad": {"r": 10, "t": 87}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top" + } + ] + + sliders_dict = { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "font": {"size": 20}, + "visible": True, + "xanchor": "right" + }, + "transition": {"duration": 300, "easing": "cubic-in-out"}, + "pad": {"b": 10, "t": 50}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [] + } + + # make data + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[0], dates[1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + fig_dict["data"].append(pie) + i+=1 + + # make frames + for x in range(1,len(dates)-1): + frame = {"data": [], "name": str(dates[x])} + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[x], dates[x+1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + frame["data"].append(pie) + i+=1 + fig_dict["frames"].append(frame) + slider_step = {"args": [ + [dates[x]], + {"frame": {"duration": 300, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 300}} + ], + "label": dates[x], + "method": "animate"} + sliders_dict["steps"].append(slider_step) + + fig_dict["layout"]["sliders"] = [sliders_dict] + + fig = go.Figure(fig_dict) + return fig + + def create_dataframe(self): + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + + def divide_dates(self, start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + + def filter_dates(self, result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + def run(self, debug=False, port=8050): + self.app.run_server(host="0.0.0.0", debug=debug, port=port) + +if __name__ == "__main__": + yt = YoutubeTrendsStats() + yt.run(port=8055) \ No newline at end of file From b457ac296f368bc20b923390c64a662211b48013 Mon Sep 17 00:00:00 2001 From: Caroline Devaux Date: Fri, 3 Jun 2022 18:59:37 +0200 Subject: [PATCH 15/26] [FEAT] - loading data in get_data.py instead of ytb_data.py --- CDMS_trending_youtube/category/get_data.py | 59 +++++ CDMS_trending_youtube/category/youtube.py | 266 +++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 CDMS_trending_youtube/category/get_data.py create mode 100644 CDMS_trending_youtube/category/youtube.py diff --git a/CDMS_trending_youtube/category/get_data.py b/CDMS_trending_youtube/category/get_data.py new file mode 100644 index 00000000..eed00577 --- /dev/null +++ b/CDMS_trending_youtube/category/get_data.py @@ -0,0 +1,59 @@ +import pandas as pd + +list_country = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] + +def load_data(list_file, list_country): + datas = pd.DataFrame() + for i in range(len(list_file)): + data = pd.read_csv(list_file[i], sep=',') + data['country'] = list_country[i] + datas = pd.concat([datas, data]) + return datas + +def create_dataframe(): + list_file = ["category/archive/BR_youtube_trending_data.csv", + "category/archive/CA_youtube_trending_data.csv", + "category/archive/DE_youtube_trending_data.csv", + "category/archive/FR_youtube_trending_data.csv", + "category/archive/GB_youtube_trending_data.csv", + "category/archive/IN_youtube_trending_data.csv", + "category/archive/JP_youtube_trending_data.csv", + "category/archive/KR_youtube_trending_data.csv", + "category/archive/MX_youtube_trending_data.csv", + "category/archive/RU_youtube_trending_data.csv", + "category/archive/US_youtube_trending_data.csv"] + + data = load_data(list_file, list_country) + data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) + + result = data.copy() + result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') + replace_categories = { 2: 'Autos & Vehicles', + 1: 'Film & Animation', + 10: 'Music', + 15: 'Pets & Animals', + 17: 'Sports', + 18: 'Short Movies', + 19: 'Travel & Events', + 20: 'Gaming', + 21: 'Videoblogging', + 22: 'People & Blogs', + 23: 'Comedy', + 24: 'Entertainment', + 25: 'News & Politics', + 26: 'Howto & Style', + 27: 'Education', + 28: 'Science & Technology', + 29: 'Nonprofits & Activism'} + + df = result.replace({"categoryId": replace_categories}) + + return df + + +youtube_df = create_dataframe() + +youtube_df.to_pickle("./data/df_youtube.pkl") \ No newline at end of file diff --git a/CDMS_trending_youtube/category/youtube.py b/CDMS_trending_youtube/category/youtube.py new file mode 100644 index 00000000..4b66cfa0 --- /dev/null +++ b/CDMS_trending_youtube/category/youtube.py @@ -0,0 +1,266 @@ +# import required packages +import dash +from dash import dcc +from dash import html +import dash_bootstrap_components as dbc +import plotly.graph_objs as go +import numpy as np +import pandas as pd +from datetime import datetime +import plotly.express as px + +def ratio(list_country_kor, datas_year): + ratio = [] + for country in list_country_kor: + country_data = datas_year[datas_year.country == country] + country_music = country_data[country_data.categoryId == 'Music'] + country_korean = is_korean(country_music.title) + ratio.append(len(country_korean) * 100 / len(country_music)) + return ratio + +list_country_kor = ["Brésil", "Canada", "Allemagne", "France", + "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] + +d1 = "\u3131" +f1 = "\u3163" + +d2 = "\uAC00" +f2 = "\uAF00" + +d3 = "\uB000" +f3 = "\uBFE1" + +d4 = "\uC058" +f4 = "\uCFFC" + +d5 = "\uD018" +f5 = "\uD79D" + +d6 = "\u3181" +f6 = "\uCB4C" + +def is_korean(word): + l = [] + for j in word: + for i in j: + if ord(i) >= ord(d1) and ord(i) <= ord(f1): + l.append(j) + if ord(i) >= ord(d2) and ord(i) <= ord(f2): + l.append(j) + if ord(i) >= ord(d3) and ord(i) <= ord(f3): + l.append(j) + if ord(i) >= ord(d4) and ord(i) <= ord(f4): + l.append(j) + if ord(i) >= ord(d5) and ord(i) <= ord(f5): + l.append(j) + return l + + +class YoutubeTrendsStats(): + def __init__(self, application = None): + self.df = pd.read_pickle('./data/df_youtube.pkl') + self.figure = self.create_figure(self.df) + self.other_figure = self.create_figure2(self.df) + + # page layout + self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + + div_content = html.Div(children=[ + # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + + html.Div(dcc.Graph(id = 'main-graph', + figure = self.figure)), + # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), + html.Div(dcc.Graph(id = 'second-graph', + figure = self.other_figure)), + html.Br(), + dcc.Markdown(""" + Le graphique est interactif. En passant la souris sur les courbes vous avez une infobulle. + + Notes : + * La catégorie Entertainement est la plus en tendance dans la plupart des pays. + * Entre le 13/04/20021 et le 01/06/2021, la catégorie Gaming représentait 13,3%/ des tendances en France + * On peut voir que la musique Coréenne représente la majorité des musiques de plusieurs pays. + + + #### À propos + + * Sources : + * https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv + """) + + ])#, style={'display': 'inline-block', 'vertical-align': 'top'}) + + self.app.layout = div_content + self.main_layout = div_content + + def create_figure2(self, datas): + datas.trending_date = pd.to_datetime(datas.trending_date) + datas['year'] = pd.DatetimeIndex(datas['trending_date']).year + + datas_2020 = datas[datas.year == 2020] + datas_2021 = datas[datas.year == 2021] + datas_2022 = datas[datas.year == 2022] + + ratio_2020 = ratio(list_country_kor, datas_2020) + ratio_2021 = ratio(list_country_kor, datas_2021) + ratio_2022 = ratio(list_country_kor, datas_2022) + + df = pd.DataFrame(index = list_country_kor) + df['2020'] = ratio_2020 + df['2021'] = ratio_2021 + df['2022'] = ratio_2022 + + y0 = np.array(df['2020']) + fig = px.scatter(df, size=y0*5, hover_name=df.index, + title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', + opacity = 0.6, labels={ + "value": "Ratio in %", + "index": "Country", + "variable": "Year" + }) + #fig.update_layout( + # title_font_size=22, + # font_family="Serif", + # title_font_family="Times New Roman", + # title_font_color="black" + #) + #fig.update_xaxes(title_font_family="Serif") + return fig + + # define figure creation function + def create_figure(self,result): + dates = self.divide_dates("2020-08-11", "2022-03-22", 12) + + # make list of continents + countries = result['country'].unique() + print(countries) + + domains = [ + {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, + {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, + {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, + {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, + {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, + {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, + {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, + {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, + {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, + {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, + {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, + {'x': [0.75, 1.0], 'y': [0.66, 1.0]} + ] + #countries = ["France", "Canada"] + # make figure + fig_dict = { + "data": [], + "layout": {}, + "frames": [] + } + + # fill in most of layout + fig_dict["layout"]["title"] = "Évolution des proportions des catégories youtube en tendances dans le monde" + fig_dict["layout"]["height"] = 700 + fig_dict["layout"]["hovermode"] = "closest" + fig_dict["layout"]["updatemenus"] = [ + { + "buttons": [ + { + "args": [None, {"frame": {"duration": 1000, "redraw": True}, + "fromcurrent": True, "transition": {"duration": 300, + "easing": "quadratic-in-out"}}], + "label": "Play", + "method": "animate" + }, + { + "args": [[None], {"frame": {"duration": 0, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 0}}], + "label": "Pause", + "method": "animate" + } + ], + "direction": "left", + "pad": {"r": 10, "t": 87}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top" + } + ] + + sliders_dict = { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "font": {"size": 20}, + "visible": True, + "xanchor": "right" + }, + "transition": {"duration": 300, "easing": "cubic-in-out"}, + "pad": {"b": 10, "t": 50}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [] + } + + # make data + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[0], dates[1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + fig_dict["data"].append(pie) + i+=1 + + # make frames + for x in range(1,len(dates)-1): + frame = {"data": [], "name": str(dates[x])} + i = 0 + for country in countries: + res_filtered = self.filter_dates(result, dates[x], dates[x+1]) + pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) + frame["data"].append(pie) + i+=1 + fig_dict["frames"].append(frame) + slider_step = {"args": [ + [dates[x]], + {"frame": {"duration": 300, "redraw": True}, + "mode": "immediate", + "transition": {"duration": 300}} + ], + "label": dates[x], + "method": "animate"} + sliders_dict["steps"].append(slider_step) + + fig_dict["layout"]["sliders"] = [sliders_dict] + + fig = go.Figure(fig_dict) + return fig + + def divide_dates(self, start, end, N): + test_date1 = datetime.strptime(start, '%Y-%m-%d') + test_date2 = datetime.strptime(end, '%Y-%m-%d') + temp = [] + diff = ( test_date2 - test_date1) // N + for idx in range(0, N+1): + temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) + # format + return temp + + def filter_dates(self, result, start_date, end_date): + after_start_date = result["trending_date"] >= start_date + before_end_date = result["trending_date"] <= end_date + between_two_dates = after_start_date & before_end_date + filtered_dates = result.loc[between_two_dates] + return filtered_dates + + def run(self, debug=False, port=8050): + self.app.run_server(host="0.0.0.0", debug=debug, port=port) + +if __name__ == "__main__": + yt = YoutubeTrendsStats() + yt.run(port=8055) \ No newline at end of file From f17e4f078e16a9045c7d36766788019ec78f4ef6 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:06:55 +0200 Subject: [PATCH 16/26] no lib bootstrap --- CDMS_trending_youtube/category/youtube.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/CDMS_trending_youtube/category/youtube.py b/CDMS_trending_youtube/category/youtube.py index 4b66cfa0..25bf1a74 100644 --- a/CDMS_trending_youtube/category/youtube.py +++ b/CDMS_trending_youtube/category/youtube.py @@ -1,8 +1,6 @@ # import required packages -import dash from dash import dcc from dash import html -import dash_bootstrap_components as dbc import plotly.graph_objs as go import numpy as np import pandas as pd @@ -63,7 +61,7 @@ def __init__(self, application = None): self.other_figure = self.create_figure2(self.df) # page layout - self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) + self.app = application#dash.Dash() div_content = html.Div(children=[ # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), @@ -119,13 +117,6 @@ def create_figure2(self, datas): "index": "Country", "variable": "Year" }) - #fig.update_layout( - # title_font_size=22, - # font_family="Serif", - # title_font_family="Times New Roman", - # title_font_color="black" - #) - #fig.update_xaxes(title_font_family="Serif") return fig # define figure creation function @@ -134,7 +125,6 @@ def create_figure(self,result): # make list of continents countries = result['country'].unique() - print(countries) domains = [ {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, @@ -150,7 +140,6 @@ def create_figure(self,result): {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, {'x': [0.75, 1.0], 'y': [0.66, 1.0]} ] - #countries = ["France", "Canada"] # make figure fig_dict = { "data": [], @@ -263,4 +252,4 @@ def run(self, debug=False, port=8050): if __name__ == "__main__": yt = YoutubeTrendsStats() - yt.run(port=8055) \ No newline at end of file + yt.app.run(port=8055) \ No newline at end of file From 51199c26d7a1d71aa5fd875c311300f89e117561 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:25:43 +0200 Subject: [PATCH 17/26] architecture --- CDMS_trending_youtube/category/ytb_data.py | 315 ------------------ .../{category => }/get_data.py | 0 .../{category => }/youtube.py | 0 3 files changed, 315 deletions(-) delete mode 100644 CDMS_trending_youtube/category/ytb_data.py rename CDMS_trending_youtube/{category => }/get_data.py (100%) rename CDMS_trending_youtube/{category => }/youtube.py (100%) diff --git a/CDMS_trending_youtube/category/ytb_data.py b/CDMS_trending_youtube/category/ytb_data.py deleted file mode 100644 index 37390076..00000000 --- a/CDMS_trending_youtube/category/ytb_data.py +++ /dev/null @@ -1,315 +0,0 @@ -# import required packages -import dash -from dash import dcc -from dash import html -import dash_bootstrap_components as dbc -import plotly.graph_objs as go -import numpy as np -import pandas as pd -from datetime import datetime -import plotly.express as px - -def ratio(list_country_kor, datas_year): - ratio = [] - for country in list_country_kor: - country_data = datas_year[datas_year.country == country] - country_music = country_data[country_data.categoryId == 'Music'] - country_korean = is_korean(country_music.title) - ratio.append(len(country_korean) * 100 / len(country_music)) - return ratio - -list_country = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] -list_country_kor = ["Brésil", "Canada", "Allemagne", "France", - "Royaume-Uni", "Inde", "Japon", "Méxique", "Russie", "US"] - -d1 = "\u3131" -f1 = "\u3163" - -d2 = "\uAC00" -f2 = "\uAF00" - -d3 = "\uB000" -f3 = "\uBFE1" - -d4 = "\uC058" -f4 = "\uCFFC" - -d5 = "\uD018" -f5 = "\uD79D" - -d6 = "\u3181" -f6 = "\uCB4C" - -def is_korean(word): - l = [] - for j in word: - for i in j: - if ord(i) >= ord(d1) and ord(i) <= ord(f1): - l.append(j) - if ord(i) >= ord(d2) and ord(i) <= ord(f2): - l.append(j) - if ord(i) >= ord(d3) and ord(i) <= ord(f3): - l.append(j) - if ord(i) >= ord(d4) and ord(i) <= ord(f4): - l.append(j) - if ord(i) >= ord(d5) and ord(i) <= ord(f5): - l.append(j) - return l - -def load_data(list_file, list_country): - datas = pd.DataFrame() - for i in range(len(list_file)): - data = pd.read_csv(list_file[i], sep=',') - data['country'] = list_country[i] - datas = pd.concat([datas, data]) - return datas - -class YoutubeTrendsStats(): - def __init__(self, application = None): - self.df = self.create_dataframe() - self.figure = self.create_figure(self.df) - self.other_figure = self.create_figure2(self.df) - - # page layout - self.app = dash.Dash(external_stylesheets = [dbc.themes.BOOTSTRAP]) - - div_content = html.Div(children=[ - # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), - - html.Div(dcc.Graph(id = 'main-graph', - figure = self.figure)), - # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), - html.Div(dcc.Graph(id = 'second-graph', - figure = self.other_figure)), - html.Br(), - dcc.Markdown(""" - Le graphique est interactif. En passant la souris sur les courbes vous avez une infobulle. - - Notes : - * La catégorie Entertainement est la plus en tendance dans la plupart des pays. - * Entre le 13/04/20021 et le 01/06/2021, la catégorie Gaming représentait 13,3%/ des tendances en France - * On peut voir que la musique Coréenne représente la majorité des musiques de plusieurs pays. - - - #### À propos - - * Sources : - * https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset?select=FR_youtube_trending_data.csv - """) - - ])#, style={'display': 'inline-block', 'vertical-align': 'top'}) - - self.app.layout = div_content - self.main_layout = div_content - - def create_figure2(self, datas): - datas.trending_date = pd.to_datetime(datas.trending_date) - datas['year'] = pd.DatetimeIndex(datas['trending_date']).year - - datas_2020 = datas[datas.year == 2020] - datas_2021 = datas[datas.year == 2021] - datas_2022 = datas[datas.year == 2022] - - ratio_2020 = ratio(list_country_kor, datas_2020) - ratio_2021 = ratio(list_country_kor, datas_2021) - ratio_2022 = ratio(list_country_kor, datas_2022) - - df = pd.DataFrame(index = list_country_kor) - df['2020'] = ratio_2020 - df['2021'] = ratio_2021 - df['2022'] = ratio_2022 - - y0 = np.array(df['2020']) - fig = px.scatter(df, size=y0*5, hover_name=df.index, - title='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays', - opacity = 0.6, labels={ - "value": "Ratio in %", - "index": "Country", - "variable": "Year" - }) - #fig.update_layout( - # title_font_size=22, - # font_family="Serif", - # title_font_family="Times New Roman", - # title_font_color="black" - #) - #fig.update_xaxes(title_font_family="Serif") - return fig - - # define figure creation function - def create_figure(self,result): - dates = self.divide_dates("2020-08-11", "2022-03-22", 12) - - # make list of continents - countries = result['country'].unique() - print(countries) - - domains = [ - {'x': [0.0, 0.25], 'y': [0.0, 0.33]}, - {'x': [0.0, 0.25], 'y': [0.33, 0.66]}, - {'x': [0.0, 0.25], 'y': [0.66, 1.0]}, - {'x': [0.25, 0.5], 'y': [0.0, 0.33]}, - {'x': [0.25, 0.5], 'y': [0.33, 0.66]}, - {'x': [0.25, 0.5], 'y': [0.66, 1.0]}, - {'x': [0.5, 0.75], 'y': [0.0, 0.33]}, - {'x': [0.5, 0.75], 'y': [0.33, 0.66]}, - {'x': [0.5, 0.75], 'y': [0.66, 1.0]}, - {'x': [0.75, 1.0], 'y': [0.0, 0.33]}, - {'x': [0.75, 1.0], 'y': [0.33, 0.66]}, - {'x': [0.75, 1.0], 'y': [0.66, 1.0]} - ] - #countries = ["France", "Canada"] - # make figure - fig_dict = { - "data": [], - "layout": {}, - "frames": [] - } - - # fill in most of layout - fig_dict["layout"]["title"] = "Évolution des proportions des catégories youtube en tendances dans le monde" - fig_dict["layout"]["height"] = 700 - fig_dict["layout"]["hovermode"] = "closest" - fig_dict["layout"]["updatemenus"] = [ - { - "buttons": [ - { - "args": [None, {"frame": {"duration": 1000, "redraw": True}, - "fromcurrent": True, "transition": {"duration": 300, - "easing": "quadratic-in-out"}}], - "label": "Play", - "method": "animate" - }, - { - "args": [[None], {"frame": {"duration": 0, "redraw": True}, - "mode": "immediate", - "transition": {"duration": 0}}], - "label": "Pause", - "method": "animate" - } - ], - "direction": "left", - "pad": {"r": 10, "t": 87}, - "showactive": False, - "type": "buttons", - "x": 0.1, - "xanchor": "right", - "y": 0, - "yanchor": "top" - } - ] - - sliders_dict = { - "active": 0, - "yanchor": "top", - "xanchor": "left", - "currentvalue": { - "font": {"size": 20}, - "visible": True, - "xanchor": "right" - }, - "transition": {"duration": 300, "easing": "cubic-in-out"}, - "pad": {"b": 10, "t": 50}, - "len": 0.9, - "x": 0.1, - "y": 0, - "steps": [] - } - - # make data - i = 0 - for country in countries: - res_filtered = self.filter_dates(result, dates[0], dates[1]) - pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) - fig_dict["data"].append(pie) - i+=1 - - # make frames - for x in range(1,len(dates)-1): - frame = {"data": [], "name": str(dates[x])} - i = 0 - for country in countries: - res_filtered = self.filter_dates(result, dates[x], dates[x+1]) - pie = go.Pie(labels=res_filtered[res_filtered["country"] == country]["categoryId"], domain = domains[i], title=country, textinfo='none', hole=.6) - frame["data"].append(pie) - i+=1 - fig_dict["frames"].append(frame) - slider_step = {"args": [ - [dates[x]], - {"frame": {"duration": 300, "redraw": True}, - "mode": "immediate", - "transition": {"duration": 300}} - ], - "label": dates[x], - "method": "animate"} - sliders_dict["steps"].append(slider_step) - - fig_dict["layout"]["sliders"] = [sliders_dict] - - fig = go.Figure(fig_dict) - return fig - - def create_dataframe(self): - list_file = ["category/archive/BR_youtube_trending_data.csv", - "category/archive/CA_youtube_trending_data.csv", - "category/archive/DE_youtube_trending_data.csv", - "category/archive/FR_youtube_trending_data.csv", - "category/archive/GB_youtube_trending_data.csv", - "category/archive/IN_youtube_trending_data.csv", - "category/archive/JP_youtube_trending_data.csv", - "category/archive/KR_youtube_trending_data.csv", - "category/archive/MX_youtube_trending_data.csv", - "category/archive/RU_youtube_trending_data.csv", - "category/archive/US_youtube_trending_data.csv"] - - data = load_data(list_file, list_country) - data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) - - result = data.copy() - result['publishedAt'] = pd.to_datetime(result['publishedAt'], format='%Y-%m-%d') - replace_categories = { 2: 'Autos & Vehicles', - 1: 'Film & Animation', - 10: 'Music', - 15: 'Pets & Animals', - 17: 'Sports', - 18: 'Short Movies', - 19: 'Travel & Events', - 20: 'Gaming', - 21: 'Videoblogging', - 22: 'People & Blogs', - 23: 'Comedy', - 24: 'Entertainment', - 25: 'News & Politics', - 26: 'Howto & Style', - 27: 'Education', - 28: 'Science & Technology', - 29: 'Nonprofits & Activism'} - - df = result.replace({"categoryId": replace_categories}) - - return df - - def divide_dates(self, start, end, N): - test_date1 = datetime.strptime(start, '%Y-%m-%d') - test_date2 = datetime.strptime(end, '%Y-%m-%d') - temp = [] - diff = ( test_date2 - test_date1) // N - for idx in range(0, N+1): - temp.append((test_date1 + idx * diff).strftime("%Y-%m-%d")) - # format - return temp - - def filter_dates(self, result, start_date, end_date): - after_start_date = result["trending_date"] >= start_date - before_end_date = result["trending_date"] <= end_date - between_two_dates = after_start_date & before_end_date - filtered_dates = result.loc[between_two_dates] - return filtered_dates - - def run(self, debug=False, port=8050): - self.app.run_server(host="0.0.0.0", debug=debug, port=port) - -if __name__ == "__main__": - yt = YoutubeTrendsStats() - yt.run(port=8055) \ No newline at end of file diff --git a/CDMS_trending_youtube/category/get_data.py b/CDMS_trending_youtube/get_data.py similarity index 100% rename from CDMS_trending_youtube/category/get_data.py rename to CDMS_trending_youtube/get_data.py diff --git a/CDMS_trending_youtube/category/youtube.py b/CDMS_trending_youtube/youtube.py similarity index 100% rename from CDMS_trending_youtube/category/youtube.py rename to CDMS_trending_youtube/youtube.py From a13ec4593f63d047de0d98a1b935dcb51f0fcc14 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 20:03:18 +0200 Subject: [PATCH 18/26] Create README.md --- CDMS_trending_youtube/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CDMS_trending_youtube/README.md diff --git a/CDMS_trending_youtube/README.md b/CDMS_trending_youtube/README.md new file mode 100644 index 00000000..c7e35ed9 --- /dev/null +++ b/CDMS_trending_youtube/README.md @@ -0,0 +1,9 @@ +## The dataset that was required for this category was not available due to a great amont of datas ! + +- Download the complete dataset within this link (it requires a Kaggle account thow) : +https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset/download +- Check if the dataset is about 800Mo. +- Extract the .zip you downloaded +- Put the extracted directory `archive` in the `tmp`. The `youtube.py` needs a reference to the dataset in a directory names `archive/*.csv` + + From bde7a1002ab23f3d865f65aeb31ff87f8d277052 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 20:04:55 +0200 Subject: [PATCH 19/26] Update README.md --- CDMS_trending_youtube/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CDMS_trending_youtube/README.md b/CDMS_trending_youtube/README.md index c7e35ed9..296ff030 100644 --- a/CDMS_trending_youtube/README.md +++ b/CDMS_trending_youtube/README.md @@ -1,4 +1,4 @@ -## The dataset that was required for this category was not available due to a great amont of datas ! +## The dataset that was required was not pushable due to a great amont of datas ! - Download the complete dataset within this link (it requires a Kaggle account thow) : https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset/download From bb5057dbd73fdda39299832743386db73e667d48 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 21:05:07 +0200 Subject: [PATCH 20/26] Update get_data.py --- CDMS_trending_youtube/get_data.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CDMS_trending_youtube/get_data.py b/CDMS_trending_youtube/get_data.py index eed00577..7f4765a2 100644 --- a/CDMS_trending_youtube/get_data.py +++ b/CDMS_trending_youtube/get_data.py @@ -14,17 +14,17 @@ def load_data(list_file, list_country): return datas def create_dataframe(): - list_file = ["category/archive/BR_youtube_trending_data.csv", - "category/archive/CA_youtube_trending_data.csv", - "category/archive/DE_youtube_trending_data.csv", - "category/archive/FR_youtube_trending_data.csv", - "category/archive/GB_youtube_trending_data.csv", - "category/archive/IN_youtube_trending_data.csv", - "category/archive/JP_youtube_trending_data.csv", - "category/archive/KR_youtube_trending_data.csv", - "category/archive/MX_youtube_trending_data.csv", - "category/archive/RU_youtube_trending_data.csv", - "category/archive/US_youtube_trending_data.csv"] + list_file = ["tmp/archive/BR_youtube_trending_data.csv", + "tmp/archive/CA_youtube_trending_data.csv", + "tmp/archive/DE_youtube_trending_data.csv", + "tmp/archive/FR_youtube_trending_data.csv", + "tmp/archive/GB_youtube_trending_data.csv", + "tmp/archive/IN_youtube_trending_data.csv", + "tmp/archive/JP_youtube_trending_data.csv", + "tmp/archive/KR_youtube_trending_data.csv", + "tmp/archive/MX_youtube_trending_data.csv", + "tmp/archive/RU_youtube_trending_data.csv", + "tmp/archive/US_youtube_trending_data.csv"] data = load_data(list_file, list_country) data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) @@ -56,4 +56,4 @@ def create_dataframe(): youtube_df = create_dataframe() -youtube_df.to_pickle("./data/df_youtube.pkl") \ No newline at end of file +youtube_df.to_pickle("./data/df_youtube.pkl") From c8710c68ddc99b7b791b3e5e060616e456b7b671 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 21:08:00 +0200 Subject: [PATCH 21/26] Update youtube.py --- CDMS_trending_youtube/youtube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CDMS_trending_youtube/youtube.py b/CDMS_trending_youtube/youtube.py index 25bf1a74..98542eab 100644 --- a/CDMS_trending_youtube/youtube.py +++ b/CDMS_trending_youtube/youtube.py @@ -56,7 +56,7 @@ def is_korean(word): class YoutubeTrendsStats(): def __init__(self, application = None): - self.df = pd.read_pickle('./data/df_youtube.pkl') + self.df = pd.read_pickle('CDMS_trending_youtube/data/df_youtube.pkl') self.figure = self.create_figure(self.df) self.other_figure = self.create_figure2(self.df) @@ -252,4 +252,4 @@ def run(self, debug=False, port=8050): if __name__ == "__main__": yt = YoutubeTrendsStats() - yt.app.run(port=8055) \ No newline at end of file + yt.app.run(port=8055) From 6252cac42334790a5390fc82040d879b05e6f563 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Sun, 5 Jun 2022 21:10:54 +0200 Subject: [PATCH 22/26] Update README.md --- CDMS_trending_youtube/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CDMS_trending_youtube/README.md b/CDMS_trending_youtube/README.md index 296ff030..bf96849d 100644 --- a/CDMS_trending_youtube/README.md +++ b/CDMS_trending_youtube/README.md @@ -4,6 +4,9 @@ https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset/download - Check if the dataset is about 800Mo. - Extract the .zip you downloaded +- Create a directory tmp `mkdir tmp` at the root of `CDMS_trending_youtube` +- Create a directory data `mkdir data` at the root of `CDMS_trending_youtube` - Put the extracted directory `archive` in the `tmp`. The `youtube.py` needs a reference to the dataset in a directory names `archive/*.csv` +- Run the `get_data.py` file From 3637aa89de513325ed2a558fb8457415f67fc71b Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Fri, 1 Jul 2022 16:13:04 +0200 Subject: [PATCH 23/26] delta --- delta.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/delta.py b/delta.py index 50f0d9ea..82e5b3a5 100644 --- a/delta.py +++ b/delta.py @@ -7,7 +7,7 @@ from energies import energies from population import population from deces import deces -from category import ytb_data +from CDMS_trending_youtube import youtube from MC_AB_consommationEtProductionEnergétique import petrole from SG_AH_pollution_des_transports import pollution from pbmc_accidents_routiers import pbmc_accidents_routiers as pbmc @@ -138,7 +138,7 @@ def init(): ukr = ukraine.Ukraine(app) c_i = corp_impact.CorporateImpact(app) popfr = dash_pop.Population(app) - cat = ytb_data.YoutubeTrendsStats(app) + ytb_trd = youtube.YoutubeTrendsStats(app) # pint = pib.Pib(app) # external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] @@ -214,7 +214,7 @@ def init(): dcc.Link(html.Button('Ukraine', style={'width':"100%"}), href='/ukraine'), dcc.Link(html.Button('Corporate Envt Impact', style={'width':"100%"}), href='/corp_impact'), dcc.Link(html.Button('Population Française', style={'width':"100%"}), href='/popfr'), - dcc.Link(html.Button('Trending Youtube', style={'width':"100%"}), href='/category'), + dcc.Link(html.Button('Trending Youtube', style={'width':"100%"}), href='/ytb_trd'), # dcc.Link(html.Button('Accès à Internet vs PIB', style={'width':"100%"}), href='/pib'), html.Br(), html.Br(), @@ -380,8 +380,8 @@ def display_page(pathname): return c_i.main_layout elif pathname == '/popfr': return popfr.main_layout - elif pathname == '/category': - return cat.main_layout + elif pathname == '/ytb_trd': + return ytb_trd.main_layout # elif pathname == "/pib": # return pint.main_layout else: From ef121b565e05f8c5c0ec0aa6760a7d035d3b66f9 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Fri, 1 Jul 2022 18:48:11 +0200 Subject: [PATCH 24/26] fix --- CDMS_trending_youtube/get_data.py | 28 +++++++++++++++++++++++++++- CDMS_trending_youtube/youtube.py | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CDMS_trending_youtube/get_data.py b/CDMS_trending_youtube/get_data.py index 7f4765a2..d17bd2b6 100644 --- a/CDMS_trending_youtube/get_data.py +++ b/CDMS_trending_youtube/get_data.py @@ -1,4 +1,17 @@ import pandas as pd +from zipfile import ZipFile +import os + +os.environ['KAGGLE_USERNAME'] = "matthieuschlienger" +os.environ['KAGGLE_KEY'] = "1fae511f2e110e02b5ace27a55a50fec" + +from kaggle.api.kaggle_api_extended import KaggleApi +api = KaggleApi() +api.authenticate() +api.dataset_download_files('rsrishav/youtube-trending-video-dataset', path="./tmp") +with ZipFile('./tmp/youtube-trending-video-dataset.zip', 'r') as zipObj: + # Extract all the contents of zip file in current directory + zipObj.extractall('./tmp') list_country = ["Brésil", "Canada", "Allemagne", "France", "Royaume-Uni", "Inde", "Japon", "Corée", "Méxique", "Russie", "US"] @@ -13,7 +26,7 @@ def load_data(list_file, list_country): datas = pd.concat([datas, data]) return datas -def create_dataframe(): +'''def create_dataframe(): list_file = ["tmp/archive/BR_youtube_trending_data.csv", "tmp/archive/CA_youtube_trending_data.csv", "tmp/archive/DE_youtube_trending_data.csv", @@ -25,6 +38,19 @@ def create_dataframe(): "tmp/archive/MX_youtube_trending_data.csv", "tmp/archive/RU_youtube_trending_data.csv", "tmp/archive/US_youtube_trending_data.csv"] +''' +def create_dataframe(): + list_file = ["tmp/BR_youtube_trending_data.csv", + "tmp/CA_youtube_trending_data.csv", + "tmp/DE_youtube_trending_data.csv", + "tmp/FR_youtube_trending_data.csv", + "tmp/GB_youtube_trending_data.csv", + "tmp/IN_youtube_trending_data.csv", + "tmp/JP_youtube_trending_data.csv", + "tmp/KR_youtube_trending_data.csv", + "tmp/MX_youtube_trending_data.csv", + "tmp/RU_youtube_trending_data.csv", + "tmp/US_youtube_trending_data.csv"] data = load_data(list_file, list_country) data.drop(columns=['channelId', 'description','thumbnail_link','video_id'], inplace=True, axis=1) diff --git a/CDMS_trending_youtube/youtube.py b/CDMS_trending_youtube/youtube.py index 98542eab..34c98762 100644 --- a/CDMS_trending_youtube/youtube.py +++ b/CDMS_trending_youtube/youtube.py @@ -1,4 +1,5 @@ # import required packages +import dash from dash import dcc from dash import html import plotly.graph_objs as go @@ -56,19 +57,28 @@ def is_korean(word): class YoutubeTrendsStats(): def __init__(self, application = None): - self.df = pd.read_pickle('CDMS_trending_youtube/data/df_youtube.pkl') + + try: + self.df = pd.read_pickle('./data/df_youtube.pkl') + except FileNotFoundError: + self.df = pd.read_pickle('./CDMS_trending_youtube/data/df_youtube.pkl') self.figure = self.create_figure(self.df) self.other_figure = self.create_figure2(self.df) # page layout - self.app = application#dash.Dash() + if application: + self.app = application + # application should have its own layout and use self.main_layout as a page or in a component + else: + self.app = dash.Dash() + div_content = html.Div(children=[ - # html.H3(children='Évolution des proportions des catégories youtube en tendances dans le monde'), + html.Div(dcc.Graph(id = 'main-graph', figure = self.figure)), - # html.H3(children='Pourcentage de musique coréenne en tendance sur le total de musique en tendance pour chaque pays'), + html.Div(dcc.Graph(id = 'second-graph', figure = self.other_figure)), html.Br(), @@ -252,4 +262,4 @@ def run(self, debug=False, port=8050): if __name__ == "__main__": yt = YoutubeTrendsStats() - yt.app.run(port=8055) + yt.run(port=8055) From 8a692ed01a10bde84c297aa218875b774c8391a0 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Fri, 1 Jul 2022 18:51:54 +0200 Subject: [PATCH 25/26] mkdir data --- CDMS_trending_youtube/get_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CDMS_trending_youtube/get_data.py b/CDMS_trending_youtube/get_data.py index d17bd2b6..b1667aa7 100644 --- a/CDMS_trending_youtube/get_data.py +++ b/CDMS_trending_youtube/get_data.py @@ -82,4 +82,6 @@ def create_dataframe(): youtube_df = create_dataframe() +os.mkdir("./data") + youtube_df.to_pickle("./data/df_youtube.pkl") From 812deee4cc751924e4cfdaff03f5f28e9453e0e5 Mon Sep 17 00:00:00 2001 From: mattmaxXXX <63347958+mattmaxXXX@users.noreply.github.com> Date: Fri, 1 Jul 2022 19:10:46 +0200 Subject: [PATCH 26/26] readme --- CDMS_trending_youtube/README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CDMS_trending_youtube/README.md b/CDMS_trending_youtube/README.md index bf96849d..c23c2230 100644 --- a/CDMS_trending_youtube/README.md +++ b/CDMS_trending_youtube/README.md @@ -1,12 +1,7 @@ ## The dataset that was required was not pushable due to a great amont of datas ! -- Download the complete dataset within this link (it requires a Kaggle account thow) : +- Here is the link of the kaggle dataset : https://www.kaggle.com/datasets/rsrishav/youtube-trending-video-dataset/download -- Check if the dataset is about 800Mo. -- Extract the .zip you downloaded -- Create a directory tmp `mkdir tmp` at the root of `CDMS_trending_youtube` -- Create a directory data `mkdir data` at the root of `CDMS_trending_youtube` -- Put the extracted directory `archive` in the `tmp`. The `youtube.py` needs a reference to the dataset in a directory names `archive/*.csv` -- Run the `get_data.py` file +- Run the `get_data.py` file to download and extract the datas