Análisis del comportamiento tuitero de tu congresista

Como ya saben, hace unos días terminó la discusión, e idas y venidas, acerca del grupo de trabajo de derechos humanos del Congreso, presidido por la congresista fujimorista Martha Chavez (@MarthaChavezK36).

La discusión degeneró tanto que llegó al tuiter. La congresista Martha Chavez anunciaba en tuiter sus planes de trabajo dentro de la comisión y respondía a uno que otro insulto tuitero. Era notable la cantidad de tuits emitidos por la congresista. Pero, fueron muchos tuits? pocos? en qué horas acostumbra tuitar la congresista?

Usando herramientas de Linux, Python y unas cuantas librerías «open source» podemos analizar el comportamiento tuitero de Martha Chavez.

Descargué del tuiter los 3200 tuits más recientes de la congresista. Para eso usé un cliente de tuiter usable desde la consola Linux.

t timeline -c -n 3200 MarthaChavezK36 > MarthaChavezK36.csv

Aquí ven parte de los tuits descargados (click para ampliar).

3200 tuits más recientes de Martha Chavez

Hice un gráfico del número de tuits por día, usando Python.

timeline de la congresista Martha Chavez

timeline de la congresista Martha Chavez

Este timeline comienza el 24 de julio. Vemos que tuvo bastante actividad el 28 de Julio, mediados de Septiembre (cuando se discutía sobre la unión civil de parejas del mismo sexo), primera y segunda semana de Octubre (en esa época se tuiteaba sobre la renuncia de Fujimori por fax), primera semana de Noviembre (cuando se armó el chongo de su elección como coordinadora del grupo de trabajo sobre derechos humanos).

Parece que su destitución del grupo de DDHH no hizo que Martha Chavez tuitee tanto como cuando se hablaba de la unión civil (muy revelador!).

Pero supongo que Martha Chavez tuitea en sus horas libres, cuando ya terminó sus horas de trabajo en el congreso, además de los fines de semana.

Podemos ver esto si usamos sus tuits para generar un «punchcard»:

python analizar_tuits.py MarthaChavezK36.csv | python punchcard.py -f punchcard_Martha_Chavez.png

horas de tuiteo de Martha Chavez

Esto es alucinante! La congresista tuitea todos los días de la semana. Tuitea a forro entre las 8 y 10 de la mañana (ni bien llega al Congreso?). Tuitea con mayor fuerza los días Viernes. El menor número de tuits a la 1:00pm hace suponer que a esa hora almuerza. Sábados y Domingos, no descansa, tuitea tanto como los días lunes. Y parece que se va a dormir a la 1:00 am. Al parecer duerme menos de 8 horas (eso no es saludable congresista!).

Este nivel de tuits emitidos por Martha Chavez es muy alto? muy bajo? Podemos hacer una comparación con un tuitero consumado, neto y nato. Comparemos con el Útero de Marita:

Este es el punchcard del utero.pe.

punchcard uterope

Vemos que, al parecer, el útero.pe tuitea menos que la congresista. Uterope tuitea muy poco los viernes, sábados y domingos (a excepción de las 9:00pm cuando tuitea con furia, debe ser que a esa hora pasan los noticieros dominicales). Qué hace el uterope los viernes y fines de semanas que no tuitea? Debe tener buena vida. También tuitea bastante los jueves.

Aqui les dejo el código necesario para hacer este tipo de análisis (?) con cualquier tuitero. Pero fíjense que el tuitero no ande borrando sus tuits ni use tuits programados ya que malograría el «análisis».

Sección geek

Código para producir el gráfico timeline y producir las fechas en formato unix, necesarias para dibujar el punchcard. El programa que hace el punchard lo saqué de aquí: https://github.com/aaronjorbin/punchcard.py


#! /usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import codecs
import re
import datetime
import time
from itertools import groupby
import numpy as np
import matplotlib.pyplot as plt
import brewer2mpl
f = codecs.open(sys.argv[1].strip(), "r", "utf-8")
datos = f.readlines()
f.close()
timestamps = []
counting = []
x = []
for line in datos:
line = line.strip()
if re.search("^[0-9]{6,},", line):
line = line.split(",")
fecha = line[1]
unix_time = time.mktime(datetime.datetime.strptime(fecha, "%Y-%m-%d %H:%M:%S +%f").timetuple())
# correct for local time Lima -5 hours
unix_time -= 60*60*5
print unix_time
fecha = fecha.split(" ")[0]
my_time = datetime.datetime.strptime(fecha, "%Y-%m-%d")
if my_time not in timestamps:
timestamps.append(my_time)
counting.append(fecha)
if fecha not in x:
x.append(fecha)
# de reversa
timestamp = timestamps[::-1]
y_axis = [len(list(group)) for key, group in groupby(counting)]
# queremos color
set2 = brewer2mpl.get_map('Set2', 'qualitative', 8).mpl_colors
color = set2[0]
fig, ax = plt.subplots(1)
plt.plot(timestamps, y_axis, color=color)
plt.xticks(rotation="45")
plt.ylabel(u"Número de tuits por día")
plt.title(u'Actividad tuitera de Martha Chavez: timeline')
plt.tight_layout()
plt.savefig("timeline" + sys.argv[1].strip() + ".png")
sys.exit()

Tuitbot para enfrentar trolls

Parece que hay un troll en tuiter que me escribe cada vez que menciono los #narcoindultos. Este troll es aprista y le gusta alabar a los principales líderes del APRA (ya sea por tuiter, o comentando en artículos de la revista Caretas).

Como contestar a los trolls da un poco de flojera, decidí fabricar un bot que lo haga por mí. Este bot es un programa muy simple, escrito en Python, que responde a aquellos que deciden trolear mi cuenta de tuiter @AniversarioPeru.

Para esto modifiqué un muy simple chat-bot que encontré aquí: http://pythonism.wordpress.com/2010/04/18/a-simple-chatbot-in-python/.

Este bot necesita ser entrenado, osea hay que hacerlo «leer» unos cuantos libros para que sepa qué responder a los trolls.

Decidí hacer mi bot picaresco pero inteligente. Entonces le hice leer las Tradiciones Peruanas de don Ricardo Palma (se bajan el libro de aquí).

Podría hacer otro bot que tenga lenguaje un poco achorado. Para eso, su entrenamiento consistiría en hacerlo leer las columnas antiguas del malapalabrero.

En resumen, este bot busca si algún troll ha empezado a fastidiar por tuiter. Si esto es cierto, responderá al troll con frases sacadas de las Tradiciones Peruanas. Este bot es muy simple y las respuestas no tienen relación con lo que haya escrito el troll. Incluso, las respuestas no tienen mucho sentido. Supongo que será suficiente para que el troll se canse de fastidiar.

Lo bueno de tener un tuitbot es que interactuaría con los trolls mientras yo estoy durmiendo, en la discoteca o de vacaciones. El bot se activa cada media hora, pero si la cantidad de trolls aumenta, podría activarse cada 10 minutos.

Tengo planes de ir mejorando poco a poco este bot. Por ahora usa una aproximación a la cadena Márkov para construir su respuesta. Por ejemplo podría emplear estadísticas Bayesianas para entrenar el bot. Lo podría entrenar para que emita saludos, preguntas, afirmaciones, despedidas, insultos etc. El bot podría usar una aproximación bayesiana para darse cuenta si el troll ha emitido una pregunta, o un insulto y responder como amerite.

La ventaja de la inferencia Bayesiana es que al analizar las palabras de un tuit, puede ir aumentando o disminuyendo la probabilidad que el tuit sea —por ejemplo— saludo o insulto. Asumamos que un tuit tenga las palabras «caviar + rojo» hay probabilidad que el troll esté insultando. Si además el tuit contiene signos de interrogación repetidos, existe alta probabilidad que este tuit sea un insulto. Los estadísticos bayesianos llaman a esto último «probabilidad posterior».

Idealmente el bot analizaría cada tuit y decidiría qué responder si la probabilidad posterior indica que he sido insultado. Tendría que hacer que mi bot se entrene leyendo los tuits que le endilgan los trolls a @rmapalacios.

Aquí hay un ejemplo de cómo sería una interacción entre un troll y mi tuitbot actual (en rojo las respuestas del bot).

>ola k ase
de Lima títere que el refrán: más allá esas calles. de su límite, de cucarachas. de la tía carnal; estas palabras:
>Que triste tu vida                               
esposa. de una vaca, de asiento, de la guitarra. de oro, de que le da colores--palabras que le sucede un tanto como hacemos
>mermelada que defiendes!!!                                                  
el reinado de la mazamorra, de grave en una mujer del rey. de nuestras abuelas eran una formidable invasión de plano.
>arriba alianza!
de la jerga franciscana. de la Monclova y el guardián del mando, de recibido varios de los jesuítas.
>matemáticamente podemos clasificar al mundial
de 1796 se plantaron en el Callao constituyó en su lado, de 1746, de aquel oportuno encarcelar, de una soberana paliza

Sección geek

Aqui les dejo el código completo del tuitbot. Requiere haber instalado un cliente de tuiter que se pueda usar desde una consola de comandos. En un post anterior detallé como usar el cliente llamado «t».

También requiere tener instalado Python. Y configurar el programa cron para que este tuitbot se active cada 30 minutos en búsqueda de tuits emitido por trolls (en este post explico como configurar cron).

Una vez que un troll es identificado, es necesario incluir el nombre de usuario en la lista de trolls para que el tuitbot sepa a quién dirigir su verbo florido.

Este es el programa que sirve para entrenar al bot. Se puede usar cualquier texto (de preferencia uno o más libros).


import pickle
import sys
# Modified from http://pythonism.wordpress.com/2010/04/18/a-simple-chatbot-in-python/
b = open('tradiciones_peruanas.txt')
text = []
for line in b:
line = line.strip()
for word in line.split():
text.append(word)
b.close()
textset = list(set(text))
follow={}
for w in range(len(text)-1):
check = text[w]
next_word = text[w+1]
print check
print next_word
if check[-1] not in '(),.?!':
if follow.has_key(check):
follow[check].append(next_word)
else:
follow[check] = [next_word]
a = open('lexicon-luke','wb')
pickle.dump(follow,a,2)
a.close()

Este es el programa que se encarga de recibir un mensaje y responder.


import pickle
import random
import sys
import re
# Modified from http://pythonism.wordpress.com/2010/04/18/a-simple-chatbot-in-python/
a = open('lexicon-luke','rb')
successorlist = pickle.load(a)
a.close()
avoid = ["a", "en", "el", "que", "la", "del", "esta", "de", "su", "con"]
def nextword(a):
if a in successorlist:
try:
return random.choice(successorlist[a])
except:
return ''
else:
return 'de'
def get_response(input):
response = ''
s = random.choice(input.split())
while True:
neword = nextword(s)
if neword != '':
response += neword + ' '
s = neword
if len(response) > 120:
for i in avoid:
regex = re.compile('\s%s\s*$'%i, re.I)
if regex.search(response):
response = regex.sub("", response).strip()
break
return response.strip()
if __name__ == "__main__":
speech = ''
while speech != 'quit':
speech = raw_input('>')
response = get_response(speech)
print response

view raw

lukebot.py

hosted with ❤ by GitHub

Este es el tuitbot que se encarga de leer lo que emiten los trolls y responderles via tuiter.


#!/usr/bin/env python
# -*- coding: UTF8 -*-
import codecs;
import os;
import datetime;
import re;
import bitly;
import subprocess;
import time;
import sys;
from random import choice;
import lukebot
debug = 0;
saved_tuits_file = "/data/projects/aniversario_peru/saved_tuits.txt"
# t update "mi primer tuit"
cmd2 = '/usr/local/bin/t set active AniversarioPeru'
p = subprocess.check_call(cmd2, shell=True);
def get_last_tuit(user):
if debug:
cmd = "cat debug_tuits.txt | grep -i " + user + " | head -n 1"
p = subprocess.check_output(cmd, shell=True);
else:
cmd = "/usr/local/bin/t mentions -l -c | grep -i " + user + " | head -n 1"
p = subprocess.check_output(cmd, shell=True);
if len(p) > 0:
p_ = p.split(",")
tuit_id = p_[0]
tuit_date = p_[1]
message = p.split(',"')[1]
message = message.replace('"', '')
message = re.sub("\s*@AniversarioPeru\s*", "", message, re.I)
return {
"tuit_id": tuit_id,
"tuit_date": tuit_date,
"user": user,
"message": message
}
return "none"
def is_new_tuit(tuit):
# file full of tuits
f = open(saved_tuits_file, "r")
file = f.readlines();
f.close()
is_new_tuit = "true"
for line in file:
line = line.strip()
line = line.split(",")
print line
if line[0] == tuit['tuit_id'] and line[2] == tuit['user']:
is_new_tuit = "false"
return is_new_tuit
def get_message(frases_to_reply):
return choice(frases_to_reply)
def reply(tuit):
#message = get_message(frases_to_reply)
message = lukebot.get_response(tuit['message'])
cmd = "/usr/local/bin/t reply " + tuit['tuit_id'] + " '" + message + "'"
p = subprocess.check_call(cmd, shell=True);
users = [
"addTrollUser"
]
for user in users:
tuit = get_last_tuit(user)
if tuit != "none":
# is the last tuit a new one?
if is_new_tuit(tuit) == "true":
reply(tuit)
# save tuit
f = open(saved_tuits_file, "a")
f.write(tuit['tuit_id'] + "," + tuit['tuit_date'] + "," + tuit['user'] + "\n")
f.close()
else:
print "we replied already"

view raw

tuit_chat.py

hosted with ❤ by GitHub

Te recuerdo tu narcoindulto usando tuitbot

https://twitter.com/otravezandres/status/355674291142021120/photo/1

En posts anteriores describí un método para averiguar en qué fecha del segundo gobierno de Alan García se había dado indultos y conmutaciones de penas a personajes que habían reincidido en el delito. Podemos construir un bot en twitter para que tuitee recordatorios el día que fueron indultados/conmutados de pena cada uno de estos angelitos. El tuit podría tener la siguiente estructura:

Como hoy (24 Jul 2009) indulté a Fulano Mengano, pero luego fue encarcelado por ESTE DELITO

Luego de usar herramientas de Linux para buscar nombres de la lista de reincidentes en todas las normas jurídicas emitidas por el Ministerio de Justica, habíamos obtenido una lista de 138 coincidencias:

02-07-09.txt:8. ABATE LUCIANO, LUCIO, conmutarle de 05 años 06 meses a 03 años 07 meses de
14-01-09.txt:56. AGUILAR TALAVERANO, JUAN CARLOS o AGUILA TALAVERANO, JUAN CARLOS,
12-05-09.txt:18. ALARCON MORA, MIGUEL ANGEL, conmutarle de 08 años a 06 años 08 meses de
28-01-10.txt:43. AGUIRRE ANGELES, ELVIS EDWIN, conmutarle de 04 años a 02 años de pena
27-02-09.txt:6. AGUIRRE ANGULO, OMAR WILLIAMS, conmutarle de 07 años a 04 años 04 meses
30-04-09.txt:7. ALCARRAZ CAMPOS, ABRAHAM, conmutarle de 05 años a 03 años 08 meses de
17-04-10.txt:13. ALMEYDA LEON, CESAR ARTURO, conmutarle de 05 años a 03 años de pena
17-06-09.txt:45. AMPARADO AMPARADO, ROLANDO WALTER o PAJARITO AMPARADO,
29-01-10.txt:18. ALVAREZ RENGIFO, LUIS ALFONSO, conmutarle de 10 años a 04 años 06
16-12-08.txt:61. ANDAVIZA CHILET, NILDA KARINA, conmutarle de 13 años a 10 años 10 meses de
...

Usando un comando de Linux podemos convertir esa lista, en un formato que sea más manejable:.

sed -i 's/\(.\+\)\.txt:[0-9]*\.*\s\([A-Z]\+\s[A-Z]\+,\s[A-Z]\+\).\+/\1 \2/g' coincidencias.txt

Lo que resulta en:

02-07-09 ABATE LUCIANO, LUCIO
14-01-09 AGUILAR TALAVERANO, JUAN
12-05-09 ALARCON MORA, MIGUEL
28-01-10 AGUIRRE ANGELES, ELVIS
27-02-09 AGUIRRE ANGULO, OMAR
30-04-09 ALCARRAZ CAMPOS, ABRAHAM
17-04-10 ALMEYDA LEON, CESAR
17-06-09 AMPARADO AMPARADO, ROLANDO
29-01-10 ALVAREZ RENGIFO, LUIS
16-12-08 ANDAVIZA CHILET, NILDA
...

Este comando convierte la fecha en formato internacional YYY-MM-DD y agrega algo de texto:

cat coincidencias.txt | awk -F ' ' '{print $1 " | " $2 " " $3 " " $4 " es indultado por Alan García, reincide en el delito poco despues | " $1}' | sed 's/^\([0-9]\{2\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\)/20\3-\2-\1/g' > tmp.txt

Lo que produce:

2009-07-02 | ABATE LUCIANO, LUCIO es indultado por Alan García, reincide en el delito poco después | 02-07-09
2009-01-14 | AGUILAR TALAVERANO, JUAN es indultado por Alan García, reincide en el delito poco después | 14-01-09
2009-05-12 | ALARCON MORA, MIGUEL es indultado por Alan García, reincide en el delito poco después | 12-05-09
2010-01-28 | AGUIRRE ANGELES, ELVIS es indultado por Alan García, reincide en el delito poco después | 28-01-10
2009-02-27 | AGUIRRE ANGULO, OMAR es indultado por Alan García, reincide en el delito poco después | 27-02-09
2009-04-30 | ALCARRAZ CAMPOS, ABRAHAM es indultado por Alan García, reincide en el delito poco después | 30-04-09
2010-04-17 | ALMEYDA LEON, CESAR es indultado por Alan García, reincide en el delito poco después | 17-04-10
2009-06-17 | AMPARADO AMPARADO, ROLANDO es indultado por Alan García, reincide en el delito poco después | 17-06-09
2010-01-29 | ALVAREZ RENGIFO, LUIS es indultado por Alan García, reincide en el delito poco después | 29-01-10
2008-12-16 | ANDAVIZA CHILET, NILDA es indultado por Alan García, reincide en el delito poco después | 16-12-08

Hay que convertir la fecha que aparece al final de cada línea a un link hacia la norma del Ministerio de Justicia correspondiente:

cat tmp.txt | sed -r 's/([0-9]{2})-([0-9]{2})-([0-9]{2})$/http:\/\/spij.minjus.gob.pe\/Normas\/textos\/\1\2\3T\.pdf/g' > coincidencias.txt

Y ya tenemos listos nuestros tuis:

2009-07-02 | ABATE LUCIANO, LUCIO es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/020709T.pdf
2009-01-14 | AGUILAR TALAVERANO, JUAN es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/140109T.pdf
2009-05-12 | ALARCON MORA, MIGUEL es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/120509T.pdf
2010-01-28 | AGUIRRE ANGELES, ELVIS es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/280110T.pdf
2009-02-27 | AGUIRRE ANGULO, OMAR es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/270209T.pdf
2009-04-30 | ALCARRAZ CAMPOS, ABRAHAM es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/300409T.pdf
2010-04-17 | ALMEYDA LEON, CESAR es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/170410T.pdf
2009-06-17 | AMPARADO AMPARADO, ROLANDO es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/170609T.pdf
2010-01-29 | ALVAREZ RENGIFO, LUIS es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/290110T.pdf
2008-12-16 | ANDAVIZA CHILET, NILDA es indultado por Alan García, reincide en el delito poco después | http://spij.minjus.gob.pe/Normas/textos/161208T.pdf
...

Ahora necesitamos crear un tuitbot

Necesitamos crear una cuenta en twitter, y una cuenta en bit.ly para que convierta los links a una versión corta. Instalamos el programa llamado t que puede tuitear desde la línea de comandos de Linux (lo descargan de aquí y sigan las intrucciones). Luego de autenticar el programa t podemos hacer un tuit con el siguiente comando:

t update "Este es mi primer tuit"

Ahora necesitamos un script que vaya línea por línea de nuestro archivo coincidencias.txt y compare la fecha actual con la fecha en que se emitieron los indultos/conmutaciones. Si las fechas coinciden en mes y dia, se emite el tuit. Escribí ese script usando el lenguage Python y aquí está:

Archivo tuit.py:

#!/usr/bin/env python
# -*- coding: UTF8 -*-
import codecs;
import os;
import datetime;
import re;
import bitly;
import subprocess;
import time;

# This script will look for an event matching today's date and will tuit it
# using https://github.com/sferik/t twitter's client

# t update "mi primer tuit"

# api details for bitly
API_USERNAME = 'Usar tu propio username'
API_KEY      = 'Usar tu propia api_key para bitly'

# get current date mm-dd
today = str(datetime.date.today());
today = re.sub("^[0-9]{4}-", "", today);

# create a log file
file_log = open("log.txt", "a");

# read data
file = "coincidencias.txt";
data_file = codecs.open(file, "r", encoding="utf-8");

def format_date(date):
    date = date.strip()
    d = datetime.datetime.strptime(date, "%Y-%m-%d")
    try:
        return d.strftime("%d de %b %Y");
    except:
        date = date.split("-");
        if date[1] == "07":
            month = "Jul";
        return re.sub("^0", "", date[2]) + " de " + month + " " + date[0]

tuits = []

# process data
for line in data_file:
    line = line.split("|");
    # date as mm-dd
    date = re.sub("^[0-9]{4}-", "", line[0].strip())
    if date == today:
        event = line[1].strip()

        link = line[2].strip();
        shortUrl = bitly.Api(login=API_USERNAME, apikey=API_KEY).shorten(link)

        formatted_date = format_date(line[0]);
        f.write(formatted_date + "\n")

        tuit = formatted_date + ": " + event + " " + shortUrl;

        cmd = '/usr/local/bin/t update "' + tuit + '"';
        tuits.append(cmd)

# count number of tuits for today
n_tuits = len(tuits)
if n_tuits > 0:
    timeToSleep = 6.0*60*60/n_tuits
else:
    timeToSleep = 1;

print "N tuits: " + str(n_tuits)
file_log.write("N tuits: " + str(n_tuits) + "\n");

print "Time to sleep between tuits: " + str(timeToSleep)
file_log.write("Time to sleep between tuits: " + str(timeToSleep) + "\n");

for cmd in tuits:
    p = subprocess.check_call(cmd, shell=True);

    if p == 0:
        # sleep for some time within 6 hour shift
        time.sleep(timeToSleep)

data_file.close();
file_log.close();

Si hay varios tuits para tuitear por día, este script esperará unas horas entre cada tuit. Para no tener que correr el programa manualmente todos los días podemos utilizar un cronjob para que se ejecute automaticamente todos los días. Aprovechando que la computadora de mi oficia está siempre prendida, puedo crear un cronjob:

crontab -l
00 08 * * * python tuit.py

En este caso el script se ejecutará automáticamente todos los días a las 8:00 am y emitirá tuits parecidos a este:

24 de Jul 2011: URSULA VELASQUEZ, PAREJA es indultada por Alan García, reincide en el delito poco después bit.ly/142iKgw