Code
# Print the number of tweets analyzed
print(f"Número de tweets analizados: {len(df)}")Número de tweets analizados: 79887
Fernanda Aguirre
January 15, 2024
Cantidad de tweets para cada uno de los eventos monitoreados:
Periodo cubierto para cada evento
# Define a function to get the date range of a given event
def get_date_range(df, event):
    event_data = df.loc[df["event"] == event]
    return event_data["dt_date"].min(), event_data["dt_date"].max()
# Get the date ranges for each event
debate1_min, debate1_max = get_date_range(df, "1er debate")
debate2_min, debate2_max = get_date_range(df, "2do debate")
elecciones_gen_min, elecciones_gen_max = get_date_range(df, "elecciones generales")
debate_balotaje_min, debate_balotaje_max = get_date_range(df, "debate balotaje")
elecciones_balotaje_min, elecciones_balotaje_max = get_date_range(
    df, "elecciones balotaje"
)
# Print the date ranges
print(f"El primer debate contempla tweets desde {debate1_min} hasta {debate1_max}")
print(f"El segundo debate contempla tweets desde {debate2_min} hasta {debate2_max}")
print(
    f"Las elecciones generales contempla tweets desde {elecciones_gen_min} hasta {elecciones_gen_max}"
)
print(
    f"El debate del balotaje contempla tweets desde {debate_balotaje_min} hasta {debate_balotaje_max}"
)
print(
    f"Las elecciones del balotaje contempla tweets desde {elecciones_balotaje_min} hasta {elecciones_balotaje_max}"
)El primer debate contempla tweets desde 2023-09-30 hasta 2023-10-02
El segundo debate contempla tweets desde 2023-10-07 hasta 2023-10-09
Las elecciones generales contempla tweets desde 2023-10-19 hasta 2023-10-23
El debate del balotaje contempla tweets desde 2023-11-11 hasta 2023-11-13
Las elecciones del balotaje contempla tweets desde 2023-11-16 hasta 2023-11-20
NOTA: Un tweet puede tener diversas etiquetas
# Subset of dataframe `df` containing only rows with non-null values in "label" and "to_journalist" columns
attacks = df.dropna(subset=["label", "to_journalist"])
# Print the number of rows in `attacks` dataframe
print(
    f"En los datos se identificaron {len(attacks)} publicaciones etiquetadas como ataques."
)En los datos se identificaron 4319 publicaciones etiquetadas como ataques.
to_journalist
@diegobranca       712
@Cris_noticias     427
@JonatanViale      421
@Angelalerena      409
@edufeiok          399
@rialjorge         261
@odonnellmaria     258
@guadavazquez      216
@robdnavarro       209
@vivicanosaok      159
@luisnovaresio     154
@cyngarciaradio    152
@rominamanguel      95
@majulluis          85
@mjolivan           72
@Gatosylvestre      66
@nbg__              59
@NANCYPAZOS         58
@ischargro          27
@anaecorrea         21
@ertenembaum        11
@hindelita          10
@juliamengo          7
@aleberco            7
@alfleuco            4
@maclorena           4
@Marcelitaojeda      3
@MercedesFunes       3
@Sietecase           3
@negropolisok        2
@wwnicolas           2
@soyingridbeck       2
@monigps             1
Name: count, dtype: int64
NOTA: Clasificación binaria
mj_count_mentions = df[df["journalist_genre"] == "H"]["to_journalist"].count()
wj_count_mentions = df[df["journalist_genre"] == "M"]["to_journalist"].count()
mj_count_attacks = attacks[attacks["journalist_genre"] == "H"].shape[0]
wj_count_attacks = attacks[attacks["journalist_genre"] == "M"].shape[0]
mj_percentage_attacks = (mj_count_attacks / mj_count_mentions) * 100
mj_proportion = (mj_percentage_attacks / 100) * 10
mj_proportion_rounded = round(mj_proportion, 1)
wj_percentage_attacks = (wj_count_attacks / wj_count_mentions) * 100
wj_proportion = (wj_percentage_attacks / 100) * 10
wj_proportion_rounded = round(wj_proportion, 1)
print(
    f"Por cada 10 menciones, los periodistas hombres recibieron {mj_proportion_rounded} ataques"
)
print(
    f"Por cada 10 menciones, las periodistas mujeres recibieron {wj_proportion_rounded} ataques"
)Por cada 10 menciones, los periodistas hombres recibieron 0.9 ataques
Por cada 10 menciones, las periodistas mujeres recibieron 0.8 ataques
seguidores_mujeres = [
    6000,
    800,
    400,
    100,
    207000,
    6000,
    3000,
    300,
    973000,
    726000,
    91000,
    230000,
    366000,
    226000,
    87000,
    102000,
    206000,
    261000,
    497000,
    259000,
    84000,
    248000,
    240000,
    119000,
    143000,
    91000,
    31000,
    166000,
    52000,
    52000,
    34000,
    50000,
    165000,
    289000,
]
seguidores_hombres = [
    3300000,
    2900,
    2300000,
    1300000,
    1000000,
    1000000,
    1000000,
    1000000,
    972000,
    835000,
    749000,
    728000,
    579000,
    575000,
    535000,
    418000,
    403000,
    400000,
    395000,
    233000,
]mj_followers_count = sum(seguidores_hombres) / len(seguidores_hombres)
wj_followers_count = sum(seguidores_mujeres) / len(seguidores_mujeres)
mj_attacks_count = attacks[attacks["journalist_genre"] == "H"].shape[0]
wj_attacks_count = attacks[attacks["journalist_genre"] == "M"].shape[0]
mj_proportion = (mj_attacks_count / mj_followers_count) * 1000
mj_proportion_formatted = "{:.2f}".format(mj_proportion)
wj_proportion = (wj_attacks_count / wj_followers_count) * 1000
wj_proportion_formatted = "{:.2f}".format(wj_proportion)
print(
    f"Por cada 1K seguidores, aproximadamente hubo {mj_proportion_formatted} ataques para hombres periodistas"
)
print(
    f"Por cada 1K seguidores, aproximadamente hubo {wj_proportion_formatted} ataques para mujeres periodistas"
)Por cada 1K seguidores, aproximadamente hubo 2.66 ataques para hombres periodistas
Por cada 1K seguidores, aproximadamente hubo 11.07 ataques para mujeres periodistas
to_journalist
@diegobranca      712
@JonatanViale     421
@edufeiok         399
@rialjorge        261
@robdnavarro      209
@luisnovaresio    154
@majulluis         85
@Gatosylvestre     66
@ischargro         27
@ertenembaum       11
@aleberco           7
@alfleuco           4
@Sietecase          3
@wwnicolas          2
Name: count, dtype: int64
to_journalist
@Cris_noticias     427
@Angelalerena      409
@odonnellmaria     258
@guadavazquez      216
@vivicanosaok      159
@cyngarciaradio    152
@rominamanguel      95
@mjolivan           72
@nbg__              59
@NANCYPAZOS         58
@anaecorrea         21
@hindelita          10
@juliamengo          7
@maclorena           4
@MercedesFunes       3
@Marcelitaojeda      3
@negropolisok        2
@soyingridbeck       2
@monigps             1
Name: count, dtype: int64
Porcentaje de ataques por tweets recolectados para cada evento
count_1er_debate = df[df["event"] == "1er debate"].shape[0]
count_2do_debate = df[df["event"] == "2do debate"].shape[0]
count_elecciones_gen = df[df["event"] == "elecciones generales"].shape[0]
count_debate_balotaje = df[df["event"] == "debate balotaje"].shape[0]
count_elecciones_balotaje = df[df["event"] == "elecciones balotaje"].shape[0]
count_attacks_1er_debate = attacks[attacks["event"] == "1er debate"].shape[0]
count_attacks_2do_debate = attacks[attacks["event"] == "2do debate"].shape[0]
count_attacks_elecciones_gen = attacks[
    attacks["event"] == "elecciones generales"
].shape[0]
count_attacks_debate_balotaje = attacks[attacks["event"] == "debate balotaje"].shape[0]
count_attacks_elecciones_balotaje = attacks[
    attacks["event"] == "elecciones balotaje"
].shape[0]
print(
    "El",
    round((count_attacks_1er_debate / count_1er_debate) * 100),
    "% del total de los tweets del 1er debate fueron ataques",
)
print(
    "El",
    round((count_attacks_2do_debate / count_2do_debate) * 100),
    "% del total de los tweets del 2do debate fueron ataques",
)
print(
    "El",
    round((count_attacks_elecciones_gen / count_elecciones_gen) * 100),
    "% del total de los tweets de las elecciones generales fueron ataques",
)
print(
    "El",
    round((count_attacks_debate_balotaje / count_debate_balotaje) * 100),
    "% del total de los tweets del debate del balotaje fueron ataques",
)
print(
    "El",
    round((count_attacks_elecciones_balotaje / count_elecciones_balotaje) * 100),
    "% del total de los tweets de las elecciones del balotaje fueron ataques",
)El 6 % del total de los tweets del 1er debate fueron ataques
El 5 % del total de los tweets del 2do debate fueron ataques
El 5 % del total de los tweets de las elecciones generales fueron ataques
El 5 % del total de los tweets del debate del balotaje fueron ataques
El 6 % del total de los tweets de las elecciones del balotaje fueron ataques
journalist_posts = df.dropna(subset=["from_journalist"])
men_journalist_posts = journalist_posts.loc[
    journalist_posts["journalist_genre"].isin(["M"])
]
women_journalist_posts = journalist_posts.loc[
    journalist_posts["journalist_genre"].isin(["H"])
]
print(
    f"""Tweets publicados por periodistas hombres: {len(men_journalist_posts)}\nTweets publicados por periodistas mujeres: {len(women_journalist_posts)}"""
)Tweets publicados por periodistas hombres: 1223
Tweets publicados por periodistas mujeres: 489
from_journalist
@anaecorrea         206
@guadavazquez       127
@rialjorge           99
@diegobranca         86
@SilvinaMolina       83
@rominamanguel       80
@Angelalerena        80
@nbg__               71
@NANCYPAZOS          70
@hindelita           66
@Cris_noticias       64
@Marcelitaojeda      53
@odonnellmaria       49
@Gatosylvestre       49
@soyingridbeck       48
@luisnovaresio       47
@edufeiok            46
@monigps             44
@mjolivan            39
@majulluis           37
@robdnavarro         31
@vivicanosaok        20
@cyngarciaradio      20
@MercedesFunes       20
@JonatanViale        20
@maclorena           18
@ertenembaum         16
@aleberco            15
@SANTIAGODELMORO     13
@ischargro           13
@juliamengo          13
@mafito11            12
@FlorHalfon          11
@SoleVallejos        11
@Sietecase           11
@silviafbarrio        9
@alfleuco             3
@gabycociffi          3
@negropolisok         2
@wwnicolas            2
@gabipellegrini3      1
@GabrielaWeller       1
@lucianageuna         1
@barilirodolfo        1
@deboraplager         1
Name: count, dtype: int64
men_debate1 = men_journalist_posts.loc[
    men_journalist_posts["event"].isin(["1er debate"])
]
men_count = men_debate1.groupby("dt_date").size().reset_index(name="count")
women_debate1 = women_journalist_posts.loc[
    women_journalist_posts["event"].isin(["1er debate"])
]
women_count = women_debate1.groupby("dt_date").size().reset_index(name="count")
fig = px.line()
fig.add_scatter(
    x=men_count["dt_date"],
    y=men_count["count"],
    name="Hombres",
    line=dict(color="orange"),
    hovertemplate="posts: %{y}",
)
fig.add_scatter(
    x=women_count["dt_date"],
    y=women_count["count"],
    name="Mujeres",
    line=dict(color="purple"),
    hovertemplate="posts: %{y}",
)
fig.update_layout(title="Publicaciones de periodistas durante el 1er debate", width=600)
fig.update_xaxes(type="category")
fig.show()def count_posts_by_gender(df, event):
    """
    This function takes in a DataFrame of journalist posts and an event string,
    and returns a new DataFrame with the count of posts by date for that event
    and gender.
    """
    debate_posts = df[df["event"].eq(event)]  # filter posts by event
    return (
        debate_posts.groupby("dt_date").size().reset_index(name="count")
    )  # group by date and count
# get posts for the 2nd debate for male and female journalists
men_debate2 = count_posts_by_gender(men_journalist_posts, "2do debate")
women_debate2 = count_posts_by_gender(women_journalist_posts, "2do debate")
# create a line plot of post counts by date for male and female journalists
fig = px.line()
fig.add_scatter(
    x=men_debate2["dt_date"],
    y=men_debate2["count"],
    name="Hombres",
    line=dict(color="orange"),
    hovertemplate="posts: %{y}",
)
fig.add_scatter(
    x=women_debate2["dt_date"],
    y=women_debate2["count"],
    name="Mujeres",
    line=dict(color="purple"),
    hovertemplate="posts: %{y}",
)
fig.update_layout(title="Publicaciones de periodistas durante el 2do debate", width=600)
fig.update_xaxes(type="category")
fig.show()men_elecciones_gen = men_journalist_posts.loc[
    men_journalist_posts["event"].isin(["elecciones generales"])
]
men_count = men_elecciones_gen.groupby("dt_date").size().reset_index(name="count")
women_elecciones_gen = women_journalist_posts.loc[
    women_journalist_posts["event"].isin(["elecciones generales"])
]
women_count = women_elecciones_gen.groupby("dt_date").size().reset_index(name="count")
fig = px.line()
fig.add_scatter(
    x=men_count["dt_date"],
    y=men_count["count"],
    name="Hombres",
    line=dict(color="orange"),
    hovertemplate="posts: %{y}",
)
fig.add_scatter(
    x=women_count["dt_date"],
    y=women_count["count"],
    name="Mujeres",
    line=dict(color="purple"),
    hovertemplate="posts: %{y}",
)
fig.update_layout(
    title="Publicaciones de periodistas durante las elecciones generales", width=600
)
fig.update_xaxes(type="category")
fig.show()men_debate_balotaje = men_journalist_posts.loc[
    men_journalist_posts["event"].isin(["debate balotaje"])
]
men_count = men_debate_balotaje.groupby("dt_date").size().reset_index(name="count")
women_debate_balotaje = women_journalist_posts.loc[
    women_journalist_posts["event"].isin(["debate balotaje"])
]
women_count = women_debate_balotaje.groupby("dt_date").size().reset_index(name="count")
fig = px.line()
fig.add_scatter(
    x=men_count["dt_date"],
    y=men_count["count"],
    name="Hombres",
    line=dict(color="orange"),
    hovertemplate="posts: %{y}",
)
fig.add_scatter(
    x=women_count["dt_date"],
    y=women_count["count"],
    name="Mujeres",
    line=dict(color="purple"),
    hovertemplate="posts: %{y}",
)
fig.update_layout(
    title="Publicaciones de periodistas durante el debate del balotaje", width=600
)
fig.update_xaxes(type="category")
fig.show()men_elecciones_balotaje = men_journalist_posts.loc[
    men_journalist_posts["event"].isin(["elecciones balotaje"])
]
men_count = men_elecciones_balotaje.groupby("dt_date").size().reset_index(name="count")
women_elecciones_balotaje = women_journalist_posts.loc[
    women_journalist_posts["event"].isin(["elecciones balotaje"])
]
women_count = (
    women_elecciones_balotaje.groupby("dt_date").size().reset_index(name="count")
)
fig = px.line()
fig.add_scatter(
    x=men_count["dt_date"],
    y=men_count["count"],
    name="Hombres",
    line=dict(color="orange"),
    hovertemplate="posts: %{y}",
)
fig.add_scatter(
    x=women_count["dt_date"],
    y=women_count["count"],
    name="Mujeres",
    line=dict(color="purple"),
    hovertemplate="posts: %{y}",
)
fig.update_layout(
    title="Publicaciones de periodistas durante las elecciones del balotaje", width=600
)
fig.update_xaxes(type="category")
fig.show()20 hashtags más utilizados en los ataques:
attacks["hashtags"] = attacks["text"].apply(
    lambda x: (
        np.nan
        if pd.isnull(x) or not isinstance(x, str) or len(re.findall(r"#\w+", x)) == 0
        else re.findall(r"#\w+", x)
    )
)
attacks["hashtags"] = attacks["hashtags"].apply(
    lambda x: ", ".join(x) if isinstance(x, list) else x
)
# convert dataframe column to list
hashtags = attacks["hashtags"].unique()
# remove nan items from list
hashtags = [x for x in hashtags if not pd.isna(x)]
# split items into a list based on a delimiter
hashtags = [x.split(",") for x in hashtags]
# flatten list of lists
hashtags = [item for sublist in hashtags for item in sublist]
# remove whitespaces
hashtags = list(map(lambda x: x.replace(" ", ""), hashtags))
# count items on list
hashtags_count = pd.Series(hashtags).value_counts()
# return first n rows in descending order
top_hashtags = hashtags_count.nlargest(20)
top_hashtags#SeVanParaSiempre                3
#Milei                           3
#MassaPresidente2023             2
#ElPeorGobiernoDeLaHistoria      2
#Bullrich                        2
#MassaPresidente                 2
#KirchnerismoNuncaMas            2
#elsi                            1
#VotoContraMassa                 1
#argentina                       1
#Son30Mil                        1
#PalestinaLibre                  1
#Sellamaperiodista               1
#NoAMilei                        1
#Milita60depobrezaydolara1200    1
#GORDITOLECHOSO                  1
#Noquierequeseterminelapauta     1
#Nolepaganconladeellos           1
#NoAl5toGobiernoK                1
#SeVannnnnn                      1
Name: count, dtype: int64
20 usuarios más mencionados en los ataques:
attacks["mentions"] = attacks["text"].apply(
    lambda x: (
        np.nan
        if pd.isnull(x) or not isinstance(x, str) or len(re.findall(r"@(\w+)", x)) == 0
        else re.findall(r"@(\w+)", x)
    )
)
attacks["mentions"] = attacks["mentions"].apply(
    lambda x: ", ".join(x) if isinstance(x, list) else x
)
# convert dataframe column to list
mentions = attacks["mentions"].unique()
# remove nan items from list
mentions = [x for x in mentions if not pd.isna(x)]
# split items into a list based on a delimiter
mentions = [x.split(",") for x in mentions]
# flatten list of lists
mentions = [item for sublist in mentions for item in sublist]
# remove whitespaces
mentions = list(map(lambda x: x.replace(" ", ""), mentions))
# count items on list
mentions_count = pd.Series(mentions).value_counts()
# return first n rows in descending order
top_mentions = mentions_count.nlargest(20)
top_mentionsPatoBullrich       5
SergioMassa        4
vivicanosaok       3
minsaurralde       3
JMilei             3
JonatanViale       2
Cris_noticias      2
Kicillofok         2
rialjorge          2
QuintelaRicardo    1
ursuvargues        1
PRossiOficial      1
edugbonorino       1
CarlosMaslaton     1
luisnovaresio      1
LANACION           1
c0o0ni             1
majulluis          1
robnavarro         1
herlombardi        1
Name: count, dtype: int64
Lista del top 20 de palabras más comunes y su frecuencia:
# load the spacy model for Spanish
nlp = spacy.load("es_core_news_sm")
# load stop words for Spanish
STOP_WORDS = nlp.Defaults.stop_words
# Function to filter stop words
def filter_stopwords(text):
    # lower text
    doc = nlp(text.lower())
    # filter tokens
    tokens = [
        token.text
        for token in doc
        if not token.is_stop and token.text not in STOP_WORDS and token.is_alpha
    ]
    return " ".join(tokens)
# apply function to dataframe column
attacks["text_pre"] = attacks["text"].apply(filter_stopwords)
# count items on column
token_counts = attacks["text_pre"].str.split(expand=True).stack().value_counts()[:20]
token_countsvos        544
sos        440
q          368
gordo      268
vas        189
mierda     163
milei      144
lechoso    140
gordito    138
massa      125
gente      115
zurda      114
viejo      108
cara       105
vieja      103
tenes      102
asco       102
orto        93
zurdos      90
puta        87
Name: count, dtype: int64
Técnica de modelado de tópicos con transformers y TF-IDF:
# remove urls, mentions, hashtags and numbers
p.set_options(p.OPT.URL, p.OPT.MENTION, p.OPT.NUMBER)
attacks["text_pre"] = attacks["text_pre"].apply(lambda x: p.clean(x))
# filter column
docs = attacks["text_pre"]
# calculate topics and probabilities
topic_model = BERTopic(
    language="multilingual", calculate_probabilities=True, verbose=True
)
# training
topics, probs = topic_model.fit_transform(docs)
# visualize topics
topic_model.visualize_topics()Mapa con 20 tópicos del contenido de los tweets:
# convert column to list
tweets = attacks["text_pre"].to_list()
timestamps = attacks["dt_date"].to_list()
topics_over_time = topic_model.topics_over_time(
    docs=tweets,
    timestamps=timestamps,
    global_tuning=True,
    evolution_tuning=True,
    nr_bins=20,
)
topic_model.visualize_topics_over_time(topics_over_time, top_n_topics=20)