<jupyter_start><jupyter_text>Elasticsearch explorationDans ce notebook, nous allons explorer notre index elasticsearch crée par la stack **training-app** de la config *docker-compose*<jupyter_code>from search.utils import Elasticsearch
from elasticsearch_dsl import Search
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# figure size in inches
plt.rcParams['figure.figsize'] = 15,10
sns.set_context("paper", rc={"font.size":20,"axes.titlesize":18,"axes.labelsize":14}) 
es = Elasticsearch(timeout=15)
es.indices.get_alias("*")
count_all_query = {
    "query": {
        "match_all": {}
    }
}
es.count(index="tmdb", body=count_all_query)<jupyter_output><empty_output><jupyter_text>Premier moyen de read en scrollant (pas très performant au final)<jupyter_code># response = es.search(
#     index='tmdb',
#     scroll='10m'
# )
# df_elastic = pd.DataFrame()
# while len(response['hits']['hits']):
#     # transformer les json en colonnes pandas
#     df_elastic = df.append(pd.io.json.json_normalize(response['hits']['hits']))
#     response = es.scroll(scroll_id=response['_scroll_id'], scroll='10m')<jupyter_output><empty_output><jupyter_text>Deuxième moyen de read beaucoup plus rapide via du scan<jupyter_code># s= Search(using=es,index="tmdb")
# df_elastic = pd.DataFrame((d.to_dict() for d in s.scan()))<jupyter_output><empty_output><jupyter_text>Troisième et dernier moyen trouvé via du multi-threading et l'API Sliced Scroll d'Elasticsearch<jupyter_code>from multiprocessing import Pool
import time
import json

SLICES = 5                                                                      


def dump_slice(slice_no):                                                       
    s = Search(using=es, index="tmdb")                                                               
    s = s.extra(slice={"id": slice_no, "max": SLICES})                          
    return pd.DataFrame((d.to_dict() for d in s.scan()))


while True:
    try:
        pool = Pool(SLICES)                                                          
        results = pool.map(dump_slice, range(SLICES))
        df_elastic = pd.concat(results)
    except (json.decoder.JSONDecodeError, elasticsearch.TransportError):
        time.sleep(5)
        continue
    break    
df_elastic.reset_index(inplace=True, drop=True)
df_elastic.shape
df_elastic.columns
clean_columns = ["budget", "revenue", "title", "original_language", "popularity", "overview", "release_date",
                "tagline", "vote_average", "vote_count", "id"]
dirty_columns = ["cast", "directors", "genres"]
df_elastic[dirty_columns].head()<jupyter_output><empty_output><jupyter_text>Drop de 187 lignes avec des informations nulles telles que le titre, acteurs, synopsis, etc...<jupyter_code>df_elastic.dropna(subset=dirty_columns+clean_columns,inplace=True)
df_elastic.reset_index(inplace=True, drop=True)<jupyter_output><empty_output><jupyter_text>Flattening des dirty columns, en effet le format JSON est utile pour du relationnel SQL mais dans notre cas d'exploration nope<jupyter_code>df_elastic[dirty_columns] = df_elastic[dirty_columns].applymap(lambda x: "|".join([str(json_nested["name"]) for json_nested in x]))<jupyter_output><empty_output><jupyter_text>KPI computing  Dans l'optique qu'un film serait un freelance, plusieurs questions business peuvent être posées.Ainsi la partie du notebook ci-dessous va calculer quelques KPI non exhaustives dont j'ai pu penser lors de l'analyse de l'index en question. Plusieurs optiques sont envisagées pour définir ce qu'est un freelance pour répondre à différentes questions en utilisant la donnée la plus logique pour y répondre. **Movie == Freelance AND Genres == Technologies**1. Quelles sont **les technologies les plus populaires** auprès des freelances ?2. Quelle est la distribution de l'inscription des freelance au cours des années depuis la création de la plateforme ?3. Quelle est la repartion des TJM **(budget)** des freelances sur la plateforme ? A mettre en relation avec l'ancienneté du freelance **(release_date)** et la popularité des technologies utilisées par le freelance.4. Le freelance a-t-il bien soigné son profil en décrivant bien les missions effectuées **length(overview)** ?Pour la question 3 et 4, on peut envisager un plan d'action de contact par mail du freelance s'il fait parti des mauvais élèves dans le remplissage de son profil et son TJM par rapport au reste de la communauté. **Movie == Mission AND Cast (acteurs) == Freelance**En effet si un movie est un freelance, le problème est que c'est une clé unique, or notre freelance peut apparaître plusieurs fois puisqu'il effectue différentes missions. Cette optique nous permet de répondre à plus de questions sous un nouvel angle.Le seul souci de cette approche est qu'un movie a plusieurs acteurs, du coup dans ce cas une mission aura plusieurs freelances dessus. Cela peut-être résolu en prenant seulement le premier acteur. Mais ici pour garder de la donnée complète, nous garderons cette multiplicité des freelances par mission.5. Quels sont les freelances **(cast)** qui rapportent le plus d'argent **(revenue)** lors de toutes leurs missions effectuées sur la plateforme Comet ?6. Combien de temps un freelance attend-t-il en moyenne avant de retrouver une mission sur la plateforme ?7. Les missions effectuées sont-elles plutôt longues ou courtes **(runtime)** ? Y'a-t-il eu un changement au cours des années dans les durées des missions ?  [Lien trouvé pour m'inspirer sur quelques questions à poser](https://github.com/deepak525/Investigate_TMDb_Movies/blob/master/investigate_the_TBMb_Dataset.ipynb)<jupyter_code>df = df_elastic.copy()
df.columns<jupyter_output><empty_output><jupyter_text>**Question 1. Quelles sont les technologies les plus populaires auprès des freelances ?**<jupyter_code>df["genres"] = df["genres"].str.split("|", n=100, expand=False) 
counts = pd.Series(np.concatenate(df.genres)).value_counts()
sns.barplot(x=counts.values, y=counts.index, palette="Set3")<jupyter_output><empty_output><jupyter_text>Cela peut nous donner une indication de quelle type de mission il faudra aller chercher auprès des clients.On verra qu'en envisageant les **movies comme des missions**, on pourra même **clusteriser les technologies** pour voir quels sont les combos de technologies demandé par mission, ainsi plutôt que d'avoir une vue techno par techno on aura une vue groupe de technos demandées par mission  **Question 2. Quelle est la distribution de l'inscription des freelance au cours des années depuis la création de la plateforme ?**<jupyter_code>df["year"] = pd.DatetimeIndex(df['release_date']).year
df.groupby('year').count()['id'].plot(y=plt.ylabel("Nombre de freelances"),
                                      title="Nombre d'inscriptions de freelances par année")
_ = plt.xlabel("Année d'inscription")<jupyter_output><empty_output><jupyter_text>Cela nous donne une indication de la croissance de la plateforme pour les inscriptions, on pourra également corréler ceci avec une **analyse des messages Slack.** Par exemple savoir si nos freelances restent actifs malgré une diminution des inscriptions sur un mois  **Question 3.** Quelle est la repartion des TJM **(budget)** des freelances sur la plateforme ? A mettre en relation avec l'ancienneté du freelance **(release_date)** et la popularité des technologies utilisées par le freelance.<jupyter_code>top10_techno = counts.nlargest(10)
for techno in top10_techno.index:
    df_modified = df[df.budget > 10000000]
    mask = df_modified.genres.apply(lambda x: techno in x)
    subset = df_modified[mask]

    # Draw the density plot
    sns.distplot(subset['budget']/100000, hist = False, kde = True,
                 kde_kws = {'linewidth': 3},
                 label = techno)
    
# Plot formatting
plt.legend(prop={'size': 16}, title = 'Technologies')
plt.title('Distribution des TJM pour le top 10 des technologies utilisées par les freelances')
plt.xlabel('TJM (en euros)')
_ = plt.ylabel('Densité')<jupyter_output><empty_output><jupyter_text>**Question 4.** Le freelance a-t-il bien soigné son profil en décrivant bien les missions effectuées **length(overview)** ?Le raisonnement derrière cette question est de proposer aux freelances (n'ayant pas bien rempli leur profil par leurs missions précédemment effectuées avant Comet), par mail par exemple, d'ajouter des expériences. Ou de demander si par mail si son profil a bien été complété, ce qui lui donnera plus de visibilité.Pour représenter le nombre de missions et de champs remplis sur son profil, j'utilise la longueur du synopsis du film. On aurait tout à fait pu choisir une autre approche tout aussi valable<jupyter_code>sns.violinplot(df["overview"].str.len())<jupyter_output><empty_output><jupyter_text>Cela nous donne une idée sur le nombre de missions effectuées par les freelancers mais également quels sont les freelancers avec un profil quasiment vide, qu'il faut donc relancer pour qu'ils reçoivent des offres pertinentes à l'avenir.Peut-être à mettre plus en valeur avec le rajout d'une catégorie **Graduated, Junior, Middle et Senior** via **release_date** pour obtenir un graphe plus pertinent.  **Question 5.** Quels sont les freelances **(cast)** qui rapportent le plus d'argent **(revenue)** lors de toutes leurs missions effectuées sur la plateforme Comet ?Le raisonnement serait peut-être de mettre en évidence ces profils aux freelances similaires pour qu'ils obtiennent plus de visibilités en améliorant leurs profils.Là encore à splitter par job, expérience pour pouvoir mieux identifier par type de job / expérience quels sont les profils à recommander pour s'améliorer : comme les technos recherchées / qui rapportent, etc...<jupyter_code>df["cast"] = df["cast"].str.split("|", n=100, expand=False) 
# cast_counts = pd.Series(np.concatenate(df.cast, df.revenue)).value_counts()
new_df = df[["cast", "revenue"]]
df_melted = pd.DataFrame({
    col:np.repeat(new_df[col].values, new_df["cast"].str.len())
    for col in new_df.columns.drop("cast")}).assign(**{"cast":np.concatenate(new_df["cast"].values)})
df_grouped = df_melted.groupby("cast").sum()
top10_revenue = pd.Series(index=df_grouped.index, data=df_grouped.revenue.values).nlargest(10)
sns.barplot(x=top10_revenue.values, y=top10_revenue.index, palette="Set3")<jupyter_output><empty_output><jupyter_text>**Question 6.** Combien de temps un freelance attend-t-il en moyenne avant de retrouver une mission sur la plateforme ?Un indicateur pour montrer aux freelances, le temps de battement moyen entre deux missions par exemple. Ou même en interne, cela reste une KPI intéressante pour analyser le marché des missions et de leur enchaînement par profil.<jupyter_code>new_df = df[["cast", "release_date"]]
df_melted = pd.DataFrame({
    col:np.repeat(new_df[col].values, new_df["cast"].str.len())
    for col in new_df.columns.drop("cast")}).assign(**{"cast":np.concatenate(new_df["cast"].values)})
# Convert to datetime date_release
df_melted["release_date"] = pd.DatetimeIndex(df_melted['release_date'])
# Drop empty cast
df_melted = df_melted[df_melted.cast != ""]
df_melted["keep_date"] = df_melted["release_date"]
df_melted = df_melted.sort_values(["cast", "keep_date"])
df_melted['date_diff'] = (
    df_melted
       .pop('release_date')
       .groupby(df_melted['cast'])
       .diff()
       .dt.days
       .fillna(0, downcast='infer'))
df_melted.head(20)<jupyter_output><empty_output><jupyter_text>Pour chaque acteur / freelance, on obtient ainsi le temps qu'il met pour travailler sur un nouveau film / mission. Cela nous donne ainsi un indicateur d'enchaînement des missions.En l'occurence 389 jours sur la totalité de la donnée TMDB<jupyter_code>df_melted.groupby("cast").date_diff.mean().mean()<jupyter_output><empty_output><jupyter_text>**Question 7.** Les missions effectuées sont-elles plutôt longues ou courtes **(runtime)** ? Y'a-t-il eu un changement au cours des années dans les durées des missions ?<jupyter_code>sns.violinplot(df[df["runtime"] < 250].runtime,
               title="Distribution des durées des missions sur Comet")
_ = plt.xlabel("Durée des missions effectuées sur Comet")
df["year"] = pd.DatetimeIndex(df['release_date']).year
df.groupby('year').mean()['runtime'].plot(y=plt.ylabel('Durée des missions Comet',fontsize = 13))
_ = plt.xlabel('Année',fontsize = 13)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10162109847568353, 'fraction_numerical': 0.007581256552947818, 'mean_word_length': 4.871212121212121, 'pattern_counts': {'":': 9, '<': 62, '<?xml version=': 0, '>': 62, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '243086', 'license': 'Apache-2.0', 'n_tokens_mistral': 4020, 'n_tokens_neox': 3863, 'n_words': 1234, 'path': 'docker/comet-usecase/Exploration.ipynb', 'repo_name': 'JeremyLG/elasticsearch-learning-to-rank', 'size': '12399'}