En todos los idiomas hay palabras que tienen demasiada frecuencia pero que no son muy informativas. A veces, es útil deshacerse de ellas antes de crear un modelo de ML. A este tipo de palabras se les suele denominar palabras vacías o stopwords.
Por ejemplo, los artículos, las conjunciones, proposiciones, entre otras más, son palabras vacías . Por otro lado, es posible que deseemos ampliar el conjunto estándar de palabras vacías. Por ejemplo, si consideramos un conjunto de datos de reseñas de películas, es posible que deseemos excluir palabras como cine, película, etcétera. Recordemos que en la primer clase vimos cómo crear nubes de palabras, y lo que éstas significan, así, puede ser que al crear nubes de palabras sobre las reseñas de películas, veamos que la palabra cine es la más frecuente, por lo cual se le daría mayor peso de aparición y tamaño en la nube de palabras, siendo lo anterior que se perdierá verdadera información que pudiésemos extrar de la nube.
Recordemos cómo crear una nube de palabras
# Importaciones necesarias
import pandas as pd
import matplotlib.pyplot as plt
# Importamos las palabras vacias (del ingles) que trae
# la libreria wordcloud
from wordcloud import WordCloud, STOPWORDS
# Cargamos los datos
data = pd.read_csv('IMDB_sample.csv')[['review', 'label']]
# Definimos un set con las palabras vacias
stopwords = set(STOPWORDS)
# Agregaremos algunas palabras vacias, a nuestra consideracion
# y de acuerdo al contexto del problema, al set que creamos antes.
stopwords.update(['movie', 'movies', 'film', 'films', 'watch', 'br'])
donde br
simplemente se refiere al metacaracter de salto de línea.
# Convertimos a cadena de texto la resegnia 4930
titanic = str(data.iloc[4930][0])
# Instanciamos un objeto WordCloud cambiando el color de fondo
# y configurando que no se consideren las palabras vacias,
# las cuales definimos antes
word_cloud = WordCloud(background_color='white', stopwords=stopwords)
cloud_titanic = word_cloud.generate(titanic)
# Graficamos
plt.imshow(cloud_titanic, interpolation='bilinear')
# Configuramos que los ejes no sean visibles
plt.axis('off')
plt.show()
También será útil eliminar palabras vacías al momento en el cual estemos construyendo nuestra bolsa de palabras, lo cual se puede implementar fácilmente en la función CountVectorizer
# Importamos ademas la lista predefinida de palabras vacias
# del ingles de sklearn
from sklearn.feature_extraction.text import CountVectorizer, ENGLISH_STOP_WORDS
# Podemos enriquecer, o agregar, mas palabras vacias a la lista predefinida
# de las palabras vacias de sklearn, por ejemplo:
stopwords = ENGLISH_STOP_WORDS.union(['film', 'movie', 'cinema', 'theatre'])
donde el método .union()
es el referente a la operación de unión referente a los conjuntos (sets). Continuando
# Instanciamos un objeto CountVectorizer configurando
# las palabras vacias que definimos antes
vect = CountVectorizer(stop_words=stopwords)
# Ajustamos a nuestro texto, el cual es en particular las
# resegnias del dataframe
vect.fit(data.review)
# Para crear la representacion de bolsa de palabras llamamos al metodo
# transform()
X = vect.transform(data.review)
Veamos el dataframe de las palabras versus su frecuencia de aparición
X_df = pd.DataFrame(X.toarray(), columns = vect.get_feature_names())
X_df
C:\ProgramData\Miniconda3\lib\site-packages\sklearn\utils\deprecation.py:87: FutureWarning: Function get_feature_names is deprecated; get_feature_names is deprecated in 1.0 and will be removed in 1.2. Please use get_feature_names_out instead. warnings.warn(msg, category=FutureWarning)
00 | 000 | 000s | 007 | 0080 | 0083 | 0093638 | 00am | 00pm | 00s | ... | zukovic | zulu | zuniga | zvyagvatsev | zwick | zx81 | zy | zzzzzzzzzzzz | zzzzzzzzzzzzz | â½ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
7496 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7497 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7498 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7499 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7500 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7501 rows × 45529 columns
Notamos del dataframe anterior la importancia de realizar un preprocesamiento de los datos, pues vemso que varias palabras que se muestra no son relevantes.
Un punto importante de mencionar es que, al eliminar las palabras vacías, el vocabulario generado por la bolsa de palabras disminuye.
isalpha()
: devuelve verdadero si la cadena está compuesta de solo letras y falso caso contrario.
isdigit()
: devuelve verdadero si la cadena se compone solo de dígitos.
isalnum()
: devuelve verdadero si la cadena se compone de caracteres alfanuméricos.
Por ejemplo, considerando nuestro conjunto de datos sobre las reseñas de la películas, podemos crear una nueva columna en el dataframe correspondiente para almacenar las reseñas tokenizadas, pero, por ejemplo, solo reteniendo los tokens que consten de letras
# Primero tokenizamos las resegnias
from nltk import word_tokenize
data['tokens'] = data.review.apply(lambda x: word_tokenize(x))
data.head()
review | label | tokens | |
---|---|---|---|
0 | This short spoof can be found on Elite's Mille... | 0 | [This, short, spoof, can, be, found, on, Elite... |
1 | A singularly unfunny musical comedy that artif... | 0 | [A, singularly, unfunny, musical, comedy, that... |
2 | An excellent series, masterfully acted and dir... | 1 | [An, excellent, series, ,, masterfully, acted,... |
3 | The master of movie spectacle Cecil B. De Mill... | 1 | [The, master, of, movie, spectacle, Cecil, B, ... |
4 | I was gifted with this movie as it had such a ... | 0 | [I, was, gifted, with, this, movie, as, it, ha... |
Una vez tokenizadas las reseñas, haremos un filtro para quedarnos solo con los tokens que consten de letras
data['tokens_letras'] = data.tokens.apply(lambda tokens: [token for token in tokens if token.isalpha()])
data.head()
review | label | tokens | tokens_letras | |
---|---|---|---|---|
0 | This short spoof can be found on Elite's Mille... | 0 | [This, short, spoof, can, be, found, on, Elite... | [This, short, spoof, can, be, found, on, Elite... |
1 | A singularly unfunny musical comedy that artif... | 0 | [A, singularly, unfunny, musical, comedy, that... | [A, singularly, unfunny, musical, comedy, that... |
2 | An excellent series, masterfully acted and dir... | 1 | [An, excellent, series, ,, masterfully, acted,... | [An, excellent, series, masterfully, acted, an... |
3 | The master of movie spectacle Cecil B. De Mill... | 1 | [The, master, of, movie, spectacle, Cecil, B, ... | [The, master, of, movie, spectacle, Cecil, B, ... |
4 | I was gifted with this movie as it had such a ... | 0 | [I, was, gifted, with, this, movie, as, it, ha... | [I, was, gifted, with, this, movie, as, it, ha... |
Veamos la comparación respecto a una reseña en particular
# Vemos que al final hay numeros
data.iloc[4][0]
"I was gifted with this movie as it had such a great premise, the friendship of three women bespoiled by one falling in love with a younger man.<br /><br />Intriguing.<br /><br />NOT! I hasten to add. These women are all drawn in extreme caricature, not very supportive of one another and conspiring and contriving to bring each other down.<br /><br />Anna Chancellor and Imelda Staunton could do no wrong in my book prior to seeing this, but here they are handed a dismal script and told to balance the action between slapstick and screwball, which doesn't work too well when the women are all well known professionals in a very small town.<br /><br />And for intelligent women they spend a whole pile of time bemoaning the lack of men/sex/lust in their lives. I felt much more could have been made of it given a decent script and more tension, the lesbian sub-plot went nowhere and those smoking/drinking women (all 3 in their forties???) were very unrealistic - even in the baby scene - screw the baby, gimme a cigarette! Right.<br /><br />Like I said, a shame of a waste. 4 out of 10."
# Consideramos los tokens de la columna tokens_letras
# Vemos que ahi no se consideraron los numeros
rev = ''
for l in data.iloc[4][3]:
rev += l + ' '
rev
'I was gifted with this movie as it had such a great premise the friendship of three women bespoiled by one falling in love with a younger br br br br NOT I hasten to add These women are all drawn in extreme caricature not very supportive of one another and conspiring and contriving to bring each other br br Anna Chancellor and Imelda Staunton could do no wrong in my book prior to seeing this but here they are handed a dismal script and told to balance the action between slapstick and screwball which does work too well when the women are all well known professionals in a very small br br And for intelligent women they spend a whole pile of time bemoaning the lack of in their lives I felt much more could have been made of it given a decent script and more tension the lesbian went nowhere and those women all in their forties were very unrealistic even in the baby scene screw the baby gim me a cigarette br br Like I said a shame of a waste out of '
Por otro lado, las expresiones regulares son una forma estándar de extraer ciertos caracteres de una cadena y tienen una funcionalidad importante a la hora de realizar el preprocesamiento de los datos. Recordemos que vimos un poco de expresiones regulares en la clase 4 de la primera sección.
En CountVectorizer
toma una expresión regular como argumento para el filtrado para la creación de la bolsa de palabras. El patrón por defecto que toma es \b\w\w+\b
, el cual coincide con las palabras que constan de al menos dos letras o números que están separados por límites de palabras \b
, de donde se ignorarán las palabras de una sola letra. Entonces, podemos modificar el patrón por defecto en CountVectorizer
, para lo cual escribimos
CountVectorizer(token_pattern = <patron>)
En un idioma, las palabras a menudo se derivan de otras palabras, lo que significa que diviersas palabras pueden estar compartiendo la misma raíz. A este proceso se le conoce como derivación o steamming:
La derivación se define como la transformación de palabras en sus formas de raíz, incluso si la raíz en sí no es una palabra válida en el idioma
Por ejemplo:
En general, la derivación tenderá a eliminar sufijos, formas plurales o posesivas.
La lematización es un proceso similar a la derivación, con la principal diferencia que las raíz resultantes de la lematización sí son palabras válidas en el idioma. Por ejemplo:
Así, si en el problema en cuestión lo importante es retener palabras, entonces es más conveniente utilizar la lematización, sin embargo, si queremos eficiencia, utilizar la derivación sería el camino adecuado dado que es mucho más rápido.
Un algoritmo de derivación muy utilizado es el algoritmo Porter, el cual podemos utilizar:
# Importacion necesaria
from nltk.stem import PorterStemmer
# Instanciamos
porter = PorterStemmer()
Con base en ello podemos derivar palabras escribiendo porter.stem()
como sigue
porter.stem('houses')
'hous'
La derivación es también posible utilizando otros idiomas, como el danés, holandés, francés, español, alemán, etc. Para utilizar derivaciones para idiomas distintos del inglés, como los que mencionamos antes, utilizando para ello
# Utilizaremos la derivacion para palabras en espagnol
from nltk.stem.snowball import SnowballStemmer
# Instanciamos colocando el idioma
esStemmer = SnowballStemmer('spanish')
# Por ejemplo
esStemmer.stem('jugando')
'jug'
# O tambien derivando para palabras del aleman
# Instanciamos colocando el idioma
alStemmer = SnowballStemmer('dutch')
# Por ejemplo
alStemmer.stem('beginnen')
'beginn'
Ahora, podemos también lematizar palabras escribiendo
# Importacion necesaria
from nltk.stem import WordNetLemmatizer
# Instanciamos
wnlemm = WordNetLemmatizer()
# Por ejemplo
wnlemm.lemmatize('houses')
'house'
Así, podemos agregar más columnas a nuestro dataframe colocando los tokens lematizados
# Instanciamos
wnl = WordNetLemmatizer()
# Tomamos la lista de tokens de cada resegnia, luego, retornaremos de nuevo una lista
# de tokens, pero dichos tokens estaran lematizados
data['tokens_lemma'] = data.tokens.apply(lambda l_tokens: [wnl.lemmatize(token) for token in l_tokens])
data.head()
review | label | tokens | tokens_letras | tokens_lemma | |
---|---|---|---|---|---|
0 | This short spoof can be found on Elite's Mille... | 0 | [This, short, spoof, can, be, found, on, Elite... | [This, short, spoof, can, be, found, on, Elite... | [This, short, spoof, can, be, found, on, Elite... |
1 | A singularly unfunny musical comedy that artif... | 0 | [A, singularly, unfunny, musical, comedy, that... | [A, singularly, unfunny, musical, comedy, that... | [A, singularly, unfunny, musical, comedy, that... |
2 | An excellent series, masterfully acted and dir... | 1 | [An, excellent, series, ,, masterfully, acted,... | [An, excellent, series, masterfully, acted, an... | [An, excellent, series, ,, masterfully, acted,... |
3 | The master of movie spectacle Cecil B. De Mill... | 1 | [The, master, of, movie, spectacle, Cecil, B, ... | [The, master, of, movie, spectacle, Cecil, B, ... | [The, master, of, movie, spectacle, Cecil, B, ... |
4 | I was gifted with this movie as it had such a ... | 0 | [I, was, gifted, with, this, movie, as, it, ha... | [I, was, gifted, with, this, movie, as, it, ha... | [I, wa, gifted, with, this, movie, a, it, had,... |
# Notemos, en particular, la palabras sensibilities
data.iloc[1][0][:250]
"A singularly unfunny musical comedy that artificially tries to marry the then-cutting edge rock 'n' roll explosion with the middle-class sensibilities of a suburban sitcom. The result is a jarringly dated mish-mash that will satisfy none of the audie"
# Notamos ahora que, referente a la cuarta columna sobre los tokens
# lematizados, que la palabra sensibilities ha sido cambiada
# por sensibility
rev = ''
for l in data.iloc[1][4]:
rev += l + ' '
rev[:250]
"A singularly unfunny musical comedy that artificially try to marry the then-cutting edge rock 'n ' roll explosion with the middle-class sensibility of a suburban sitcom . The result is a jarringly dated mish-mash that will satisfy none of the audienc"
La bolsa de palabras es un método bastante bueno, no obstante, a veces es posible que deseemos utilizar algún otro enfoque más sofisticado, como es el caso del algoritmo de frecuencia de término inverso-frecuencia de documentos, mejor conocido como algoritmo tf-idf.
El término de frecuencia nos dice con qué frecuencia aparece una palabra determinada en un documento del corpus, donde cada palabra tiene su propia frecuencia de término. Lo anterior nos dará un valor o puntuación denominado $tf$.
La frecuencia inversa del documento se define comúnmente como la relación lograrítmica entre el número total de documentos y el número de documentos que contiene una palabra en específico. Esto significa que la frecuencia inversa de documentos es, básicamente, que las palabras raras tendrán un valor alto de ésta. Lo anterior nos dará un valor $idf$.
Cuando multiplicamos las puntuaciones $tf$ e $idf$ obtenemos de manera global la puntuación $tf-idf$ de una palabra en el corpus.
Cabe mencionar que en la bolsa de palabras, las palabras tenían diferetes recuentos de frecuencia en los documentos, pero no contabilizamos respecto a la longitud de un documento, lo cual el algoritmo tf-idf sí hace.
tf-idf también destacará las palabras que son más interesantes y las palabras que son comunes en un documento, pero no en todos los demás.
Un ejemplo es que, si en cierto corpus tenemos que el tópico es referente a viajes y aeropuertos, entonces los nombres de las aerolíneas tendrán puntajes bajos de tf-idf, debido a que ocurren muchas veces y en muchos documentos. Si en cierto documento se habla sobre un tema referente a viajes y aeropuertos, pero en éste se discute un tema que no es discutido en muchos otros documentos, entonces el primer documento tiene una alta probabilidad de tener una puntuación tf-idf elevada.
Notemos que, dado que tf-idf penaliza las palabras frecuentes, entonces hay menos necesidad de definir explícitamente las palabras vacías. Procederemos a aplicar dicho algoritmo en Python:
# Importacion de TfidfVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
donde TfidfVectorizer
es muy similar a CountVectorizer
y recibe los mismo parámetros. Para continuar con el ejemplo, carguemos el conjunto de datos con el cual trabajaremos
data = pd.read_csv('tweets.csv')
data.head()
tweet_id | airline_sentiment | airline_sentiment_confidence | negativereason | negativereason_confidence | airline | airline_sentiment_gold | name | negativereason_gold | retweet_count | text | tweet_coord | tweet_created | tweet_location | user_timezone | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 570306133677760513 | neutral | 1.0000 | NaN | NaN | Virgin America | NaN | cairdin | NaN | 0 | @VirginAmerica What @dhepburn said. | NaN | 2015-02-24 11:35:52 -0800 | NaN | Eastern Time (US & Canada) |
1 | 570301130888122368 | positive | 0.3486 | NaN | 0.0000 | Virgin America | NaN | jnardino | NaN | 0 | @VirginAmerica plus you've added commercials t... | NaN | 2015-02-24 11:15:59 -0800 | NaN | Pacific Time (US & Canada) |
2 | 570301083672813571 | neutral | 0.6837 | NaN | NaN | Virgin America | NaN | yvonnalynn | NaN | 0 | @VirginAmerica I didn't today... Must mean I n... | NaN | 2015-02-24 11:15:48 -0800 | Lets Play | Central Time (US & Canada) |
3 | 570301031407624196 | negative | 1.0000 | Bad Flight | 0.7033 | Virgin America | NaN | jnardino | NaN | 0 | @VirginAmerica it's really aggressive to blast... | NaN | 2015-02-24 11:15:36 -0800 | NaN | Pacific Time (US & Canada) |
4 | 570300817074462722 | negative | 1.0000 | Can't Tell | 1.0000 | Virgin America | NaN | jnardino | NaN | 0 | @VirginAmerica and it's a really big bad thing... | NaN | 2015-02-24 11:14:45 -0800 | NaN | Pacific Time (US & Canada) |
donde consideraremos específicamente la columna
data.text
0 @VirginAmerica What @dhepburn said. 1 @VirginAmerica plus you've added commercials t... 2 @VirginAmerica I didn't today... Must mean I n... 3 @VirginAmerica it's really aggressive to blast... 4 @VirginAmerica and it's a really big bad thing... ... 14635 @AmericanAir thank you we got on a different f... 14636 @AmericanAir leaving over 20 minutes Late Flig... 14637 @AmericanAir Please bring American Airlines to... 14638 @AmericanAir you have my money, you change my ... 14639 @AmericanAir we have 8 ppl so we need 2 know h... Name: text, Length: 14640, dtype: object
data.text[4]
"@VirginAmerica and it's a really big bad thing about it"
Así, procedemos a ajustar TfidfVectorizer
a dicha columna
vect = TfidfVectorizer(max_features=100).fit(data.text)
X = vect.transform(data.text)
Lo cual devuelve, también, una matriz dispersa
X
<14640x100 sparse matrix of type '<class 'numpy.float64'>' with 119182 stored elements in Compressed Sparse Row format>
donde una matriz dispersa es una matriz con valores en su mayoría cero, que almacena solo los valores distintos de cero. Transformamos dicha matriz dispersa como sigue
import warnings
warnings.filterwarnings('ignore')
X_df = pd.DataFrame(X.toarray(), columns=vect.get_feature_names())
X_df
about | after | again | airline | all | am | americanair | amp | an | and | ... | was | we | what | when | why | will | with | would | you | your | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.000000 | 0.668165 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
1 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.329040 | 0.000000 |
2 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
3 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.431149 | 0.0 | 0.000000 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.332355 |
4 | 0.494872 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.279754 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
14635 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.293653 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.402305 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.268285 | 0.000000 |
14636 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.181266 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.248334 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
14637 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.487504 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
14638 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.188272 | 0.000000 | 0.0 | 0.179597 | ... | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.344014 | 0.232585 |
14639 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.164169 | 0.000000 | 0.0 | 0.000000 | ... | 0.0 | 0.449822 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.000000 |
14640 rows × 100 columns
lo cual nos arroja un resultado bastante similar al obtenido mediante la bolsa de palabras, donde cada columna es una característica y cada fila contiene la puntuación tf-idf de la característica en un tweet determinado.