diff --git a/Analysis_Bag_of_Words.md b/Analysis_Bag_of_Words.md new file mode 100644 index 0000000..6387f2a --- /dev/null +++ b/Analysis_Bag_of_Words.md @@ -0,0 +1,266 @@ +# Python Text Analysis: Bag of Words +## Term Frequency-Inverse Document Frequency +So far, we're relying on word frequency to give us information about a document. This assumes if a word appears more often in a document, it's more informative. However, this may not always be the case. For example, we've already removed stop words because they are not informative, despite the fact that they appear many times in a document. We also know the word "flight" is among the most frequent words, but it is not that informative, because it appears in many documents. Since we're looking at airline tweets, we shouldn't be surprised to see the word "flight"! + +To remedy this, we use a weighting scheme called *tf-idf* (term frequency-inverse document frequency). The big idea behind *tf-idf* is to weight a word not just by its frequency within a document, but also by its frequency in one document relative to the remaining documents. So, when we construct the DTM, we will be assigning each term a *tf-idf* score. Specifically, term *$t$* in document *$d$* is assigned a *tf-idf* score as follows: + +![image](https://github.com/user-attachments/assets/b0f88246-81b9-47a4-adbf-5610e07d2b65) + +In essence, the tf-idf score of a word in a document is the product of two components: term frequency (tf) and inverse document frequency (idf). The idf acts as a scaling factor. If a word occurs in all documents, then idf equals 1. No scaling will happen. But idf is typically greater than 1, which is the weight we assign to the word to make the tf-idf score higher, so as to highlight that the word is informative. In practice, we add 1 to both the denominator and numerator ("add-1 smooth") to prevent any issues with zero occurrences. + +We can also create a *tf-idf* DTM using *sklearn*. We'll use a *TfidfVectorizer* this time: + +~~~javascript +from sklearn.feature_extraction.text import TfidfVectorizer +~~~ +~~~javascript +# Create a tfidf vectorizer +vectorizer = TfidfVectorizer(lowercase=True, +…………………………………..stop_words='english' +…………………………………..min_df=2, +…………………………………..max_df=0.95, +…………………………………..max_features=None) +~~~ +~~~javascript +# Fit and transform +tf_dtm = vectorizer.fit_transform(tweets['text_lemmatized']) +tf_dtm +~~~ + +* + +~~~javascript +# Create a tf-idf dataframe +tfidf = pd.DataFrame(tf_dtm.todense(), +……………………………columns=vectorizer.get_feature_names_out(), +……………………………index=tweets.index) +tfidf.head() +~~~ +~~~javascript +aa aadv aadvantage aal abandon abc ability able aboard abq ... yummy yup yvonne yvr yyj yyz zero zone zoom zurich +0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.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 ... 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 ... 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 ... 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 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + +~~~ + +You may have noticed that the vocabulary size is the same as we saw in Challenge 2. This is because we used the same parameter setting when creating the vectorizer. But the values in the matrix are different—they are tf-idf scores instead of raw counts. + +# Interpret TF-IDF Values + +Let's take a look the document where a term has the highest *tf-idf* values. We'll use the *.idxmax()* method to find the index. + +~~~javascript +# Retrieve the index of the document +tfidf.idxmax() +~~~ +~~~javascript +aa 10077 +aadv 9285 +aadvantage 9974 +aal 10630 +abandon 7859 + ... +yyz 1350 +zero 2705 +zone 3177 +zoom 3920 +zurich 10622 +Length: 3571, dtype: int64 +~~~ + +For example, the term "worst" occurs most distinctively in the 918th tweet. +~~~javascript +tfidf.idxmax()['worst'] +np.int64(918) +~~~ +Recall that this is the tweet where the word "worst" appears six times! +~~~javascript +tweets['text_processed'].iloc[918] +"USER is the worst. worst reservation policies. worst costumer service. worst worst worst. congrats, USER you're not that bad!" +~~~ +How about "cancel"? Let's take a look at another example. +~~~javascript +tfidf.idxmax()['cancel'] +np.int64(5945) +~~~ +~~~javascript +tweets['text_processed'].iloc[5945] +'USER cancelled flighted 😢' +~~~ +# 🥊 Challenge 3: Words with Highest Mean TF-IDF scores + +We have obtained *tf-idf* values for each term in each document. But what do these values tell us about the sentiments of tweets? Are there any words that are particularly informative for positive/negative tweets? + +To explore this, let's gather the indices of all positive/negative tweets and calculate the mean *tf-idf* scores of words appear in each category. + +We've provided the following starter code to guide you: + +* Subset the tweets dataframe according to the airline_sentiment label and retrieve the index of each subset (.index). Assign the index to positive_index or negative_index. +* For each subset: + - Retrieve the td-idf representation + + Take the mean tf-idf values across the subset using .mean() + + Sort the mean values in the descending order using .sort_values() + + Get the top 10 terms using .head() + +Next, run pos.plot and neg.plot to plot the words with the highest mean *tf-idf* scores for each subset. + +~~~javascript +# Complete the boolean masks +positive_index = tweets[tweets['airline_sentiment'] == 'positive'].index +negative_index = tweets[tweets['airline_sentiment'] == 'negative'].index +~~~ +~~~javascript +# Complete the following two lines +pos = tfidf.loc[positive_index].mean().sort_values(ascending=False).head(10) +neg = tfidf.loc[negative_index].mean().sort_values(ascending=False).head(10) +~~~ +~~~javascript +pos.plot(kind='barh', +………….xlim=(0, 0.18), +………….color='cornflowerblue', +………….title='Top 10 terms with the highest mean tf-idf values for positive tweets'); +~~~ + +![Reto2 2](https://github.com/user-attachments/assets/a8ce6b0a-4480-4ec6-95a3-f757eafadd70) + +~~~javascript +neg.plot(kind='barh', +.………….xlim=(0, 0.18), +.………….color='darksalmon', +.………….title='Top 10 terms with the highest mean tf-idf values for negative tweets'); +~~~ +![Reto2 3](https://github.com/user-attachments/assets/18245908-21d2-46d8-b71c-5a21682f78e5) + +🔔 Question: How would you interpret these results? Share your thoughts in the chat! + +*** +*** +# Análisis de texto en Python: Bolsa de palabras +## Frecuencia de términos - Frecuencia inversa de documentos +Hasta ahora, nos basamos en la frecuencia de palabras para obtener información sobre un documento. Esto supone que si una palabra aparece con más frecuencia en un documento, es más informativa. Sin embargo, esto no siempre es así. Por ejemplo, ya hemos eliminado las palabras vacías porque no son informativas, a pesar de que aparecen muchas veces en un documento. También sabemos que la palabra "vuelo" es una de las más frecuentes, pero no es tan informativa, ya que aparece en muchos documentos. Dado que estamos analizando tweets de aerolíneas, no debería sorprendernos ver la palabra "vuelo". + +Para solucionar esto, utilizamos un esquema de ponderación llamado *tf-idf* (término frecuencia-inversa frecuencia de documento). La idea principal de *tf-idf* es ponderar una palabra no solo por su frecuencia dentro de un documento, sino también por su frecuencia en un documento en relación con los demás. Por lo tanto, al construir el DTM, asignaremos a cada término un *tf-idf* score. Específicamente, al término *$t$* del documento *$d$* se le asigna un *tf-idf* score de la siguiente manera: + +![image](https://github.com/user-attachments/assets/b0f88246-81b9-47a4-adbf-5610e07d2b65) + +En esencia, la puntuación *tf-idf* de una palabra en un documento es el producto de dos componentes: la frecuencia de término *(tf)* y la frecuencia inversa de documento *(idf)*. La *idf* actúa como un factor de escala. Si una palabra aparece en todos los documentos, la *idf* es igual a 1. No se produce escala. Sin embargo, la *idf* suele ser mayor que 1, que es el peso que asignamos a la palabra para aumentar la puntuación *tf-idf* y destacar su carácter informativo. En la práctica, sumamos 1 tanto al denominador como al numerador (add-1 smooth) para evitar problemas con cero ocurrencias. + +También podemos crear un DTM *tf-idf* usando sklearn. En esta ocasión, usaremos *TfidfVectorizer*: +~~~javascript +from sklearn.feature_extraction.text import TfidfVectorizer +~~~ +~~~javascript +# Create a tfidf vectorizer +vectorizer = TfidfVectorizer(lowercase=True, +…………………………………..stop_words='english' +…………………………………..min_df=2, +…………………………………..max_df=0.95, +…………………………………..max_features=None) +~~~ +~~~javascript +# Fit and transform +tf_dtm = vectorizer.fit_transform(tweets['text_lemmatized']) +tf_dtm +~~~ +* +~~~javascript +# Create a tf-idf dataframe +tfidf = pd.DataFrame(tf_dtm.todense(), +……………………………columns=vectorizer.get_feature_names_out(), +……………………………index=tweets.index) +tfidf.head() +~~~ +~~~javascript +aa aadv aadvantage aal abandon abc ability able aboard abq ... yummy yup yvonne yvr yyj yyz zero zone zoom zurich +0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.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 ... 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 ... 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 ... 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 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +~~~ + +Quizás hayas notado que el tamaño del vocabulario es el mismo que vimos en el Desafío 2. Esto se debe a que usamos la misma configuración de parámetros al crear la vectorización. Sin embargo, los valores de la matriz son diferentes: son puntuaciones de *tf-idf* en lugar de conteos brutos. + +# Interpretar valores de TF-IDF + +Analicemos el documento donde un término tiene los valores de *tf-idf* más altos. Usaremos el método *.idxmax()* para encontrar el índice. +~~~javascript +# Retrieve the index of the document +tfidf.idxmax() +~~~ +~~~javascript +aa 10077 +aadv 9285 +aadvantage 9974 +aal 10630 +abandon 7859 + ... +yyz 1350 +zero 2705 +zone 3177 +zoom 3920 +zurich 10622 +Length: 3571, dtype: int64 +~~~ +Por ejemplo, el término "peor" aparece de forma más clara en el tweet número 918th. +~~~javascript +tfidf.idxmax()['worst'] +np.int64(918) +~~~ +¡Recordemos que este es el tweet donde la palabra “peor” aparece seis veces! +~~~javascript +tweets['text_processed'].iloc[918] +"USER is the worst. worst reservation policies. worst costumer service. worst worst worst. congrats, USER you're not that bad!" +~~~ +¿Qué tal "Cancelar"? Veamos otro ejemplo. +~~~javascript +tfidf.idxmax()['cancel'] +np.int64(5945) +~~~ +~~~javascript +tweets['text_processed'].iloc[5945] +'USER cancelled flighted 😢' +~~~ +# 🥊 Desafío 3: Palabras con las puntuaciones medias más altas en TF-IDF +Hemos obtenido valores *tf-idf* para cada término en cada documento. Pero ¿qué nos dicen estos valores sobre el sentimiento de los tweets? ¿Hay palabras que sean especialmente informativas para los tweets positivos/negativos? +Para explorar esto, recopilemos los índices de todos los tweets positivos/negativos y calculemos la media de las puntuaciones *tf-idf* de las palabras que aparecen en cada categoría. +Hemos proporcionado el siguiente código de inicio como guía: +* Cree un subconjunto del dataframe de tweets según la etiqueta airline_sentiment y recupere el índice de cada subconjunto (.index). Asigne el índice a positive_index o a negative_index. +* Para cada subconjunto: + - Recupere la representación td-idf + - Tome la media de los valores tf-idf del subconjunto con .mean() + - Ordene la media de los valores tf-idf en orden descendente con .sort_values() + - Obtenga los 10 términos principales con .head() +A continuación, ejecute *pos.plot* y *neg.plot* para representar gráficamente las palabras con las puntuaciones medias *tf-idf* más altas para cada subconjunto. + +~~~javascript +# Complete the boolean masks +positive_index = tweets[tweets['airline_sentiment'] == 'positive'].index +negative_index = tweets[tweets['airline_sentiment'] == 'negative'].index +~~~ +~~~javascript +# Complete the following two lines +pos = tfidf.loc[positive_index].mean().sort_values(ascending=False).head(10) +neg = tfidf.loc[negative_index].mean().sort_values(ascending=False).head(10) +~~~ +~~~javascript +pos.plot(kind='barh', +………….xlim=(0, 0.18), +………….color='cornflowerblue', +………….title='Top 10 terms with the highest mean tf-idf values for positive tweets'); +~~~ +![Reto2 2](https://github.com/user-attachments/assets/a8ce6b0a-4480-4ec6-95a3-f757eafadd70) +~~~javascript +neg.plot(kind='barh', +.………….xlim=(0, 0.18), +.………….color='darksalmon', +.………….title='Top 10 terms with the highest mean tf-idf values for negative tweets'); +~~~ +![Reto2 3](https://github.com/user-attachments/assets/18245908-21d2-46d8-b71c-5a21682f78e5) + +🔔 Pregunta: ¿Cómo interpretarías estos resultados? ¡Comparte tu opinión en el chat! diff --git a/Python Text Analysi-Bag of Words.docx b/Python Text Analysi-Bag of Words.docx new file mode 100644 index 0000000..9e9884d Binary files /dev/null and b/Python Text Analysi-Bag of Words.docx differ diff --git a/Python Text Analysis.docx b/Python Text Analysis.docx new file mode 100644 index 0000000..0f9ea7c Binary files /dev/null and b/Python Text Analysis.docx differ diff --git a/Text_Analysis.md b/Text_Analysis.md new file mode 100644 index 0000000..e20a889 --- /dev/null +++ b/Text_Analysis.md @@ -0,0 +1,231 @@ +# Python Text Analysis: Preprocessing +## Remove Extra Whitespace Characters +Sometimes we might come across texts with extraneous whitespace, such as spaces, tabs, and newline characters, which is particularly common when the text is scrapped from web pages. Before we dive into the details, let's briefly introduce Regular Expressions *(regex)* and the *re package*. +Regular expressions are a powerful way of searching for specific string patterns in large corpora. They have an infamously steep learning curve, but they can be very efficient when we get a handle on them. Many NLP packages heavily rely on regex under the hood. Regex testers, such as *regex101*, are useful tools in both understanding and creating regex expressions. +Our goal in this workshop is not to provide a deep (or even shallow) dive into regex; instead, we want to expose you to them so that you are better prepared to do deep dives in the future! +The following example is a poem by William Wordsworth. Like many poems, the text may contain extra line breaks (i.e., newline characters, \n) that we want to remove. +~~~ javascript +# File path to the poem +text_path = '../data/poem_wordsworth.txt' +~~~ +~~~ javascript +# Read the poem in +with open(text_path, 'r') as file: + text = file.read() +file.close() +~~~ +As you can see, the poem is formatted as a continuous string of text with line breaks placed at the end of each line, making it difficult to read. +~~~ javascript +text +~~~ +~~~ javascript +"I wandered lonely as a cloud\n\n\nI wandered lonely as a cloud\nThat floats on high o'er vales and hills,\nWhen all at once I saw a +crowd,\nA host, of golden daffodils;\nBeside the lake, beneath the trees,\nFluttering and dancing in the breeze.\n\nContinuous as the +stars that shine\nAnd twinkle on the milky way,\nThey stretched in never-ending line\nAlong the margin of a bay:\nTen thousand saw I at a +glance,\nTossing their heads in sprightly dance.\n\nThe waves beside them danced; but they\nOut-did the sparkling waves in glee:\nA poet +could not but be gay,\nIn such a jocund company:\nI gazed—and gazed—but little thought\nWhat wealth the show to me had brought:\n\nFor +oft, when on my couch I lie\nIn vacant or in pensive mood,\nThey flash upon that inward eye\nWhich is the bliss of solitude;\nAnd then my +heart with pleasure fills,\nAnd dances with the daffodils." +~~~ +One handy function we can use to display the poem properly is *.splitlines()*. As the name suggests, it splits a long text sequence into a list of lines whenever there is a newline character. +~~~ javascript +# Split the single string into a list of lines +text.splitlines() +~~~ +~~~ javascript +['I wandered lonely as a cloud', + '', + '', + 'I wandered lonely as a cloud', + "That floats on high o'er vales and hills,", + 'When all at once I saw a crowd,', + 'A host, of golden daffodils;', + 'Beside the lake, beneath the trees,', + 'Fluttering and dancing in the breeze.', + '', + 'Continuous as the stars that shine', + 'And twinkle on the milky way,', + 'They stretched in never-ending line', + 'Along the margin of a bay:', + 'Ten thousand saw I at a glance,', + 'Tossing their heads in sprightly dance.', + '', + 'The waves beside them danced; but they', + 'Out-did the sparkling waves in glee:', + 'A poet could not but be gay,', + 'In such a jocund company:', + 'I gazed—and gazed—but little thought', + 'What wealth the show to me had brought:', + '', + 'For oft, when on my couch I lie', + 'In vacant or in pensive mood,', + 'They flash upon that inward eye', + 'Which is the bliss of solitude;', + 'And then my heart with pleasure fills,', + 'And dances with the daffodils.'] +~~~ +Let's return to our tweet data for an example. +~~~ javascript +# Print the second example +second_example = tweets['text'][5] +second_example +~~~ +~~~ javascript +@VirginAmerica seriously would pay $30 a flight for seats that didn't have this playing.\nit's really the only bad thing about flying VA" +~~~ +In this case, we don't really want to split the tweet into a list of strings. We still expect a single string of text but would like to remove the line break completely from the string. +The string method *.strip()* effectively does the job of stripping away spaces at both ends of the text. However, it won't work in our example as the newline character is in the middle of the string. +~~~ javascript +# Strip only removed blankspace at both ends +second_example.strip() +~~~ +This is where regex could be really helpful. +~~~ javascript +import re +~~~ +Now, with regex, we are essentially calling it to match a pattern that we have identified in the text data, and we want to do some operations to the matched part—extract it, replace it with something else, or remove it completely. Therefore, the way regex works could be unpacked into the following steps: + - Identify and write the pattern in regex (r'PATTERN') + - Write the replacement for the pattern ('REPLACEMENT') + - Call the specific regex function (e.g., re.sub()) +In our example, the pattern we are looking for is \s, which is the regex short name for any whitespace character (\n and \t included). We also add a quantifier + to the end: \s+. It means we'd like to capture one or more occurences of the whitespace character. +~~~ javascript +# Write a pattern in regex +blankspace_pattern = r'\s+' +~~~ +The replacement for one or more whitespace characters is exactly one single whitespace, which is the canonical word boundary in English. Any additional whitespace will be reduced to a single whitespace. +~~~ javascript +# Write a replacement for the pattern identfied +blankspace_repl = ' ' +~~~ +Lastly, let's put everything together using the function *re.sub()*, which means we want to substitute a pattern with a replacement. The function takes in three arguments—the pattern, the replacement, and the string to which we want to apply the function. +~~~ javascript +# Replace whitespace(s) with ' ' + +clean_text = re.sub(pattern = blankspace_pattern, + repl = blankspace_repl, + string = second_example) +print(clean_text) +~~~ +~~~ javascript +@VirginAmerica seriously would pay $30 a flight for seats that didn't have this playing. it's really the only bad thing about flying VA +~~~ +Ta-da! The newline character is no longer there. +*** +*** +# Análisis de texto en Python: Preprocesamiento +## Eliminar espacios en blanco innecesarios +A veces nos encontramos con textos con espacios en blanco innecesarios, como espacios, tabulaciones y caracteres de nueva línea, lo cual es particularmente común cuando el texto se extrae de páginas web. Antes de profundizar en los detalles, presentemos brevemente las expresiones regulares *(regex)* y el paquete *re*. +Las expresiones regulares son una forma eficaz de buscar patrones de cadenas específicos en campos extensos. Su curva de aprendizaje es notablemente pronunciada, pero pueden ser muy eficientes una vez que las dominamos. Muchos paquetes de PLN dependen en gran medida de las expresiones regulares. Los evaluadores de expresiones regulares, como *regex101*, son herramientas útiles tanto para comprender como para crear expresiones regulares. +Nuestro objetivo en este taller no es ofrecer una introducción profunda (ni siquiera superficial) a las expresiones regulares; en cambio, queremos presentarles para que estén mejor preparados para profundizar en el futuro. +El siguiente ejemplo es un poema de William Wordsworth. Como muchos poemas, el texto puede contener saltos de línea adicionales (es decir, caracteres de nueva línea, \n) que queremos eliminar. +~~~ javascript +# File path to the poem +text_path = '../data/poem_wordsworth.txt' +~~~ +~~~ javascript +# Read the poem in +with open(text_path, 'r') as file: + text = file.read() +file.close() +~~~ +Como puede ver, el poema está formateado como una cadena continua de texto con saltos de línea al final de cada línea, lo que dificulta su lectura. +~~~javascript +text +~~~ +~~~javascript +"Vagaba solo como una nube\n\n\nVagaba solo como una nube\nQue flota en lo alto sobre valles y colinas,\nCuando de repente vi una +multitud,\nUna multitud de narcisos dorados;\nJunto al lago, bajo los árboles,\nRevoloteando y danzando con la brisa.\n\nContinuos como +las estrellas que brillan\nY centellean en la vía láctea,\nSe extendían en una línea interminable\nA lo largo de la orilla de una +bahía:\nDiez mil vi de un vistazo,\nSacudiendo sus cabezas en una danza alegre.\n\nLas olas a su lado danzaban; pero ellas\nSuperaban a +las olas centelleantes en alegría:\nUn poeta no podía sino estar alegre,\nEn una compañía tan alegre:\nMiré, y miré, pero poco pensé\nEn +la riqueza que me había traído el espectáculo:\n\nPorque a menudo, cuando yazgo en mi lecho\nEn vacío o en estado de ánimo +pensativo,\nDestellan en ese ojo interior\nQue es la dicha de la soledad;\nY entonces mi corazón se llena de placer,\nY baila con los +narcisos." +~~~ +Una función útil para mostrar el poema correctamente es *.splitlines()*. Como su nombre indica, divide una secuencia de texto larga en una lista de líneas cuando hay un salto de línea. +~~~ javascript +# Split the single string into a list of lines +text.splitlines() +~~~ +~~~ javascript +['Vagaba solo como una nube', +'', +'', +'Vagaba solo como una nube', +"Que flota en lo alto sobre valles y colinas", +"Cuando de repente vi una multitud", +"Una multitud de narcisos dorados"; +"Junto al lago, bajo los árboles", +"Revoloteando y danzando con la brisa". +'', +"Continuos como las estrellas que brillan", +"Y centellean en la Vía Láctea", +"Se extendían en una línea interminable", +"A lo largo de la orilla de una bahía": +"Diez mil vi de un vistazo", +"Agitando sus cabezas en una danza alegre". +'', +"Las olas a su lado danzaban; pero ellos, +'Superaron en alegría a las olas centelleantes': +'Un poeta no podía sino ser alegre', +'En tan alegre compañía': +'Miré, y miré, pero poco pensé', +'Qué riqueza me había traído el espectáculo': +', +'Porque a menudo, cuando yazgo en mi lecho', +'En estado vacío o pensativo', +'Destellan en ese ojo interior', +'Que es la dicha de la soledad'; +'Y entonces mi corazón se llena de placer', +'Y baila con los narcisos'.] +~~~ +Volvamos a nuestros datos de tweets para ver un ejemplo. +~~~ javascript +# Print the second example +second_example = tweets['text'][5] +second_example +~~~ +~~~ javascript +@VirginAmerica realmente pagaría $30 por vuelo por asientos que no tuvieran esta función. Es realmente lo único malo de volar en Virginia. +~~~ +En este caso, no queremos dividir el tuit en una lista de cadenas. Seguimos esperando una sola cadena de texto, pero queremos eliminar por completo el salto de línea. +El método de cadena *.strip()* elimina eficazmente los espacios en ambos extremos del texto. Sin embargo, no funcionará en nuestro ejemplo, ya que el carácter de nueva línea está en medio de la cadena. +~~~ javascript +# Strip only removed blankspace at both ends +second_example.strip() +~~~ +~~~ javascript +"@VirginAmerica realmente pagaría $30 por un vuelo por asientos sin esta función. Es realmente lo único malo de volar con Virginia". +~~~ + +Aquí es donde las expresiones regulares pueden ser realmente útiles. +~~~ javascript +import re +~~~ +Ahora, con regex, básicamente la llamamos para que coincida con un patrón identificado en los datos de texto, y queremos realizar algunas operaciones con la parte coincidente: extraerla, reemplazarla o eliminarla por completo. Por lo tanto, el funcionamiento de *regex* se puede resumir en los siguientes pasos: + - Identificar y escribir el patrón en regex (r'PATTERN') + - Escribir el reemplazo para el patrón ('REPLACEMENT') + - Llamar a la función regex específica (p. ej., re.sub()) +En nuestro ejemplo, el patrón que buscamos es \s, que es el nombre corto en regex para cualquier espacio en blanco (incluidos \n y \t). También añadimos un cuantificador + al final: \s+. Esto significa que queremos capturar una o más ocurrencias del espacio en blanco. +~~~ javascript +# Write a pattern in regex +blankspace_pattern = r'\s+' +~~~ +El reemplazo de uno o más espacios en blanco es exactamente un solo espacio, que es el límite canónico de una palabra en inglés. Cualquier espacio adicional se reducirá a un solo espacio. +~~~ javascript +# Write a replacement for the pattern identfied +blankspace_repl = ' ' +~~~ +Finalmente, combinemos todo usando la función *re.sub()*, que significa que queremos sustituir un patrón por un reemplazo. La función acepta tres argumentos: el patrón, el reemplazo y la cadena a la que queremos aplicar la función. +~~~ javascript +# Replace whitespace(s) with ' ' +clean_text = re.sub(pattern = blankspace_pattern, + repl = blankspace_repl, + string = second_example) +print(clean_text) +~~~ +~~~ javascript +@VirginAmerica en serio pagaría $30 por vuelo por asientos que no tuvieran esta función. Es realmente lo único malo de volar con Virginia. +~~~ +¡Ta-da! El carácter de nueva línea ya no está. +