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_mentions
PatoBullrich 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_counts
vos 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)