Analizando un grupo de Whatsapp
By: Daniel Eduardo Fernandez Villalobos
Tags: Tóxico, Grupos De Whatsapp, Mcd, WhatsApp, Analysis

Photo by Rachit Tank on Unsplash

Trabajo basado en “Whatsapp Group Chat Analysis using Python and Plotly” de Saiteja Kura.

Importando datos

La aplicación de Whatsapp tiene una opción para exportar cualquier chat a un archivo de formato .txt, si consideramos una línea de nuestro archivo podremos obtener información relevante de las columnas, las cuales están conformadas por:

{fecha} {hora} {autor} {mensaje}

aaaa/mm/dd hh:mm autor mensaje

Importando librerías

Haremos un código que pueda identificar cada línea con un formato de fecha, esto haría que cada mensaje sea único. Obtendremos la fecha, la hora, el autor y el mensaje de nuestro archivo de texto.

def startsWithDateAndTime(s):

# regex pattern for date.(Works only for android. IOS Whatsapp export format is different. Will update the code soon

pattern = '^\d{2}/\d{2}/\d{2} \d{2}:\d{2} - '

result = re.match(pattern, s)

if result:

return True

return False



# Finds username of any given format.

def FindAuthor(s):

patterns = [

'([\w]+):', # First Name

'([\w]+[\s]+[\w]+):', # First Name + Last Name

'([\w]+[\s]+[\w]+[\s]+[\w]+):', # First Name + Middle Name + Last Name

'([+]\d{2} \d{5} \d{5}):', # Mobile Number (India)

'([+]\d{2} \d{3} \d{3} \d{4}):', # Mobile Number (US)

'([\w]+)[\u263a-\U0001f999]+:', # Name and Emoji

]

pattern = '^' + '|'.join(patterns)

result = re.match(pattern, s)

if result:

return True

return False



def getDataPoint(line):

splitLine = line.split(' - ')

dateTime = splitLine[0]

date, time = dateTime.split(' ')

message = ' '.join(splitLine[1:])

if FindAuthor(message):

splitMessage = message.split(': ')

author = splitMessage[0]

message = ' '.join(splitMessage[1:])

else:

author = None

return date, time, author, message parsedData = [] # List to keep track of data so it can be used by a Pandas dataframe

# Upload your file here

conversationPath = r'C:/Users/Daniel Fernandez/Desktop/Ingenieria/Analizando-un-grupo/grupo_whatsapp.txt' # chat file

with open(conversationPath, encoding="utf-8") as fp:

fp.readline() # Skipping first line of the file because contains information related to something about end-to-end encryption

messageBuffer = []

date, time, author = None, None, None

while True:

line = fp.readline()

if not line:

break

line = line.strip()

if startsWithDateAndTime(line):

if len(messageBuffer) > 0:

parsedData.append([date, time, author, ' '.join(messageBuffer)])

messageBuffer.clear()

date, time, author, message = getDataPoint(line)

messageBuffer.append(message)

else:

messageBuffer.append(line)



df = pd.DataFrame(parsedData, columns=['Date', 'Time', 'Author', 'Message']) # Initialising a pandas Dataframe.

df["Date"] = pd.to_datetime(df["Date"])

Cambiaremos los nombres de los participantes del chat y los remplazaremos por otros usando nombres de bandas de música.

# Defining names and aliases

names = list(df.Author.unique())

aliases = list(['Misfits', 'Korn', 'Slipknot', 'Queen', 'Rammstein', 'Megadeath', 'Metallica']) # Replacing for Author

df.Author.replace(names, aliases, inplace=True) # Replacing within messages

for (name, alias) in zip(names, aliases):

df.Message = df.Message.str.replace(name, alias)

Ahora nuestro grupo se ve de la siguiente forma:

df.head()

df.info() <class 'pandas.core.frame.DataFrame'>

RangeIndex: 899 entries, 0 to 898

Data columns (total 4 columns):

# Column Non-Null Count Dtype

--- ------ -------------- -----

0 Date 899 non-null datetime64[ns]

1 Time 899 non-null object

2 Author 899 non-null object

3 Message 899 non-null object

dtypes: datetime64[ns](1), object(3)

memory usage: 28.2+ KB

Podemos observar que tenemos 899 mensajes totales, y usaremos lo siguiente para descartar valores nulos.

df = df.dropna()

Estadísticas del grupo

Para encontrar el número de cuantos emojis se han usado crearemos una columna que cuente con los emojis para cada mensaje, se hará lo mismo para los URLs enviados

def split_count(text): emoji_list = []

data = regex.findall(r'\X', text)

for word in data:

if any(char in emoji.UNICODE_EMOJI for char in word):

emoji_list.append(word) return emoji_list total_messages = df.shape[0]

media_messages = df[df['Message'] == '<Media omitted>'].shape[0]

df["emoji"] = df["Message"].apply(split_count)

emojis = sum(df['emoji'].str.len())

URLPATTERN = r'(https?://\S+)'

df['urlcount'] = df.Message.apply(lambda x: re.findall(URLPATTERN, x)).str.len()

links = np.sum(df.urlcount)

Aquí se muestran la cantidad de mesanjes, emojis y URL enviados.

print("Estadisticas del grupo")

print("Mensajes:", total_messages)

print("Media:", media_messages)

print("Emojis:", emojis)

print("Links:", links) Estadisticas del grupo

Mensajes: 899

Media: 0

Emojis: 213

Links: 2

Separaremos los mensajes de multimedia omitidos en un dataframe distinto.

media_messages_df = df[df['Message'] == '<Media omitted>']

messages_df = df.drop(media_messages_df.index)

Haremos la cuenta de cuantas palabras y letras uso cada autor en su mensaje añadiendo 2 columnas al dataframe:

messages_df['Letter_Count'] = messages_df['Message'].apply(lambda s : len(s))

messages_df['Word_Count'] = messages_df['Message'].apply(lambda s : len(s.split(' ')))

Ahora nuestros datos se ven de la siguiente manera.

messages_df

Estadísticas por autor

Revisaremos por autor la cantidad de mensajes y emojis que se han utilizado

# Creates a list of unique Authors - ['Manikanta', 'Teja Kura', .........]

l = messages_df.Author.unique() for i in range(len(l)):

# Filtering out messages of particular user

req_df= messages_df[messages_df["Author"] == l[i]]

# req_df will contain messages of only one particular user

print(f'Stats of {l[i]} -')

# shape will print number of rows which indirectly means the number of messages

print('Messages Sent', req_df.shape[0])

#Word_Count contains of total words in one message. Sum of all words/ Total Messages will yield words per message

words_per_message = (np.sum(req_df['Word_Count']))/req_df.shape[0]

print('Words per message', words_per_message)

#media conists of media messages

media = media_messages_df[media_messages_df['Author'] == l[i]].shape[0]

print('Media Messages Sent', media)

# emojis conists of total emojis

emojis = sum(req_df['emoji'].str.len())

print('Emojis Sent', emojis)

#links consist of total links

links = sum(req_df["urlcount"])

print('Links Sent', links)

print() Stats of Misfits -

Messages Sent 251

Words per message 9.183266932270916

Media Messages Sent 0

Emojis Sent 63

Links Sent 1



Stats of Korn -

Messages Sent 143

Words per message 5.335664335664336

Media Messages Sent 0

Emojis Sent 54

Links Sent 0



Stats of Slipknot -

Messages Sent 41

Words per message 6.463414634146342

Media Messages Sent 0

Emojis Sent 9

Links Sent 0



Stats of Queen -

Messages Sent 31

Words per message 8.0

Media Messages Sent 0

Emojis Sent 3

Links Sent 0



Stats of Rammstein -

Messages Sent 158

Words per message 6.8734177215189876

Media Messages Sent 0

Emojis Sent 39

Links Sent 0



Stats of Megadeath -

Messages Sent 63

Words per message 5.507936507936508

Media Messages Sent 0

Emojis Sent 17

Links Sent 0



Stats of Metallica -

Messages Sent 212

Words per message 9.391509433962264

Media Messages Sent 0

Emojis Sent 28

Links Sent 1

Estadísticas de emojis

Anteriormente obtuvimos la cantidad de emojis enviados en toda la conversación del grupo, aquí veremos cuál fue la cantidad de emojis diferentes usados.

total_emojis_list = list(set([a for b in messages_df.emoji for a in b]))

total_emojis = len(total_emojis_list)

print(total_emojis) out[] 47

Emojis más usados en el grupo

Crearemos una lista con los diferentes emojis usados y cuantas veces han aparecido a lo largo de la conversación.

total_emojis_list = list([a for b in messages_df.emoji for a in b])

emoji_dict = dict(Counter(total_emojis_list))

emoji_dict = sorted(emoji_dict.items(), key=lambda x: x[1], reverse=True) emoji_df = pd.DataFrame(emoji_dict, columns=['emoji', 'count'])

emoji_df

Se harán algunas gráficas de indicar el uso de los emojis en el grupo y para cada autor.

fig = px.pie(emoji_df, values='count', names='emoji',

title='Emoji Distribution')

fig.update_traces(textposition='inside', textinfo='percent+label')

fig.show()

l = messages_df.Author.unique()

for i in range(len(l)):

dummy_df = messages_df[messages_df['Author'] == l[i]]

total_emojis_list = list([a for b in dummy_df.emoji for a in b])

emoji_dict = dict(Counter(total_emojis_list))

emoji_dict = sorted(emoji_dict.items(), key=lambda x: x[1], reverse=True)

print('Emoji Distribution for', l[i])

author_emoji_df = pd.DataFrame(emoji_dict, columns=['emoji', 'count'])

fig = px.pie(author_emoji_df, values='count', names='emoji')

fig.update_traces(textposition='inside', textinfo='percent+label')

fig.show()

Emoji Distribution for Misfits

Emoji Distribution for Korn

Emoji Distribution for Slipknot

Emoji Distribution for Queen

Emoji Distribution for Rammstein

Emoji Distribution for Megadeath

Emoji Distribution for Metallica

Nube de palabras

Una nube de palabras es una representación visual de las palabras utilizadas en un texto, donde el tamaño de la palabra es proporcional a la frecuencia en lz que fue usada en los mensajes del grupo.

text = " ".join(review for review in messages_df.Message)

print ("There are {} words in all the messages.".format(len(text))) out[] There are 40510 words in all the messages.

Tenemos una cantidad 40510 palabras en todo nuestro chat. Usaremos una lista predeterminada para las stopwords.

stopwords = set(STOPWORDS)

#stopwords.update(["ra", "ga", "na", "ani", "em", "ki", "ah","ha","la","eh","ne","le"])

# Generate a word cloud image

wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(text)

# Display the generated image:

# the matplotlib way:



plt.figure( figsize=(10,5))

plt.imshow(wordcloud, interpolation='bilinear')

plt.axis("off")

plt.show()

Algunas estadísticas mas

Veremos la cantidad de menajes enviados por días, la frecuencia de las horas y fechas en las que el grupo estuvo más activo.

date_df = messages_df.groupby("Date").sum()

date_df.reset_index(inplace=True) fig = px.line(date_df, x="Date", y="Word_Count", title='Number of Messages as time moves on.') fig.update_xaxes(nticks=20)

fig.show()

messages_df['Date'].value_counts().head(10).plot.barh()

plt.xlabel('Number of Messages')

plt.ylabel('Date')