Limpieza y preparación de datos con 🤗 Datasets
La mayoría de las veces, los datos con los que trabajas no estarán perfectamente preparados para entrenar modelos. En esta sección exploraremos las diversas características que 🤗 Datasets proporciona para limpiar tus conjuntos de datos.
Dividiendo y organizando nuestros datos[[slicing-and-dicing-our-data]]
De forma similar a Pandas, 🤗 Datasets proporciona varias funciones para manipular el contenido de los objetos Dataset y DatasetDict. Ya encontramos el método Dataset.map() en el Capítulo 3, y en esta sección exploraremos algunas de las otras funciones a nuestra disposición.
Para este ejemplo, usaremos el Conjunto de Datos de Reseñas de Medicamentos alojado en el Repositorio de Aprendizaje Automático de UC Irvine, que contiene reseñas de pacientes sobre varios medicamentos, junto con la condición tratada y una calificación de 10 estrellas de la satisfacción del paciente.
Primero necesitamos descargar y extraer los datos, lo que se puede hacer con los comandos wget y unzip:
!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip
Dado que TSV es solo una variante de CSV que usa tabulaciones en lugar de comas como separador, podemos cargar estos archivos usando el script de carga csv y especificando el argumento delimiter en la función load_dataset() de la siguiente manera:
from datasets import load_dataset
data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"}
# \t is the tab character in Python
drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t")
Una buena práctica al realizar cualquier tipo de análisis de datos es tomar una pequeña muestra aleatoria para tener una idea rápida del tipo de datos con los que estás trabajando. En 🤗 Datasets, podemos crear una muestra aleatoria encadenando las funciones Dataset.shuffle() y Dataset.select():
drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Peek at the first few examples
drug_sample[:3]
{'Unnamed: 0': [87571, 178045, 80482],
'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'],
'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"',
'"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."',
'"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'],
'rating': [9.0, 3.0, 10.0],
'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'],
'usefulCount': [36, 13, 128]}
Ten en cuenta que hemos fijado la semilla en Dataset.shuffle() para fines de reproducibilidad. Dataset.select() espera un iterable de índices, por lo que hemos pasado range(1000) para tomar los primeros 1,000 ejemplos del conjunto de datos mezclado. De esta muestra ya podemos ver algunas peculiaridades en nuestro conjunto de datos:
- La columna
Unnamed: 0parece sospechosamente un ID anonimizado para cada paciente. - La columna
conditionincluye una mezcla de etiquetas en mayúsculas y minúsculas. - Las reseñas tienen longitudes variables y contienen una mezcla de separadores de línea de Python (
\r\n) así como códigos de caracteres HTML como&\#039;.
Veamos cómo podemos usar 🤗 Datasets para lidiar con cada uno de estos problemas. Para probar la hipótesis del ID de paciente para la columna Unnamed: 0, podemos usar la función Dataset.unique() para verificar que el número de IDs coincide con el número de filas en cada división:
for split in drug_dataset.keys():
assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))
Esto parece confirmar nuestra hipótesis, así que limpiemos un poco el conjunto de datos renombrando la columna Unnamed: 0 a algo un poco más interpretable. Podemos usar la función DatasetDict.rename_column() para renombrar la columna en ambas divisiones de una sola vez:
drug_dataset = drug_dataset.rename_column(
original_column_name="Unnamed: 0", new_column_name="patient_id"
)
drug_dataset
DatasetDict({
train: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
num_rows: 161297
})
test: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
num_rows: 53766
})
})
[!TIP] ✏️ ¡Pruébalo! Usa la función
Dataset.unique()para encontrar el número de medicamentos y condiciones únicos en los conjuntos de entrenamiento y prueba.
A continuación, normalicemos todas las etiquetas condition usando Dataset.map(). Como hicimos con la tokenización en el Capítulo 3, podemos definir una función simple que se puede aplicar a todas las filas de cada división en drug_dataset:
def lowercase_condition(example):
return {"condition": example["condition"].lower()}
drug_dataset.map(lowercase_condition)
AttributeError: 'NoneType' object has no attribute 'lower'
¡Oh no, hemos encontrado un problema con nuestra función de mapeo! Del error podemos inferir que algunas de las entradas en la columna condition son None, que no se pueden convertir a minúsculas ya que no son cadenas. Eliminemos estas filas usando Dataset.filter(), que funciona de manera similar a Dataset.map() y espera una función que reciba un solo ejemplo del conjunto de datos. En lugar de escribir una función explícita como:
def filter_nones(x):
return x["condition"] is not None
y luego ejecutar drug_dataset.filter(filter_nones), podemos hacer esto en una sola línea usando una función lambda. En Python, las funciones lambda son funciones pequeñas que puedes definir sin nombrarlas explícitamente. Tienen la forma general:
lambda <arguments> : <expression>
donde lambda es una de las palabras clave especiales de Python, <arguments> es una lista/conjunto de valores separados por comas que definen las entradas de la función, y <expression> representa las operaciones que deseas ejecutar. Por ejemplo, podemos definir una función lambda simple que eleva un número al cuadrado de la siguiente manera:
lambda x : x * x
Para aplicar esta función a una entrada, necesitamos envolverla a ella y a la entrada entre paréntesis:
(lambda x: x * x)(3)
9
De manera similar, podemos definir funciones lambda con múltiples argumentos separándolos con comas. Por ejemplo, podemos calcular el área de un triángulo de la siguiente manera:
(lambda base, height: 0.5 * base * height)(4, 8)
16.0
Las funciones lambda son útiles cuando quieres definir funciones pequeñas de un solo uso (para más información sobre ellas, te recomendamos leer el excelente tutorial de Real Python de Andre Burgaud). En el contexto de 🤗 Datasets, podemos usar funciones lambda para definir operaciones simples de mapeo y filtrado, así que usemos este truco para eliminar las entradas None en nuestro conjunto de datos:
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
Con las entradas None eliminadas, podemos normalizar nuestra columna condition:
drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]
['left ventricular dysfunction', 'adhd', 'birth control']
¡Funciona! Ahora que hemos limpiado las etiquetas, echemos un vistazo a la limpieza de las reseñas.
Creando nuevas columnas[[creating-new-columns]]
Siempre que estés tratando con reseñas de clientes, una buena práctica es verificar el número de palabras en cada reseña. Una reseña podría ser solo una palabra como "¡Genial!" o un ensayo completo con miles de palabras, y dependiendo del caso de uso, deberás manejar estos extremos de manera diferente. Para calcular el número de palabras en cada reseña, usaremos una heurística aproximada basada en dividir cada texto por espacios en blanco.
Definamos una función simple que cuente el número de palabras en cada reseña:
def compute_review_length(example):
return {"review_length": len(example["review"].split())}
A diferencia de nuestra función lowercase_condition(), compute_review_length() devuelve un diccionario cuya clave no corresponde a uno de los nombres de columna en el conjunto de datos. En este caso, cuando compute_review_length() se pasa a Dataset.map(), se aplicará a todas las filas del conjunto de datos para crear una nueva columna review_length:
drug_dataset = drug_dataset.map(compute_review_length)
# Inspect the first training example
drug_dataset["train"][0]
{'patient_id': 206461,
'drugName': 'Valsartan',
'condition': 'left ventricular dysfunction',
'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"',
'rating': 9.0,
'date': 'May 20, 2012',
'usefulCount': 27,
'review_length': 17}
Como era de esperar, podemos ver que se ha añadido una columna review_length a nuestro conjunto de entrenamiento. Podemos ordenar esta nueva columna con Dataset.sort() para ver cómo son los valores extremos:
drug_dataset["train"].sort("review_length")[:3]
{'patient_id': [103488, 23627, 20558],
'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
'condition': ['birth control', 'muscle spasm', 'pain'],
'review': ['"Excellent."', '"useless"', '"ok"'],
'rating': [10.0, 1.0, 6.0],
'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'],
'usefulCount': [5, 2, 10],
'review_length': [1, 1, 1]}
Como sospechábamos, algunas reseñas contienen solo una palabra, lo que, aunque puede estar bien para el análisis de sentimientos, no sería informativo si queremos predecir la condición.
[!TIP] 🙋 Una forma alternativa de añadir nuevas columnas a un conjunto de datos es con la función
Dataset.add_column(). Esto te permite proporcionar la columna como una lista de Python o un array de NumPy y puede ser útil en situaciones en las queDataset.map()no es adecuado para tu análisis.
Usemos la función Dataset.filter() para eliminar las reseñas que contengan menos de 30 palabras. De manera similar a lo que hicimos con la columna condition, podemos filtrar las reseñas muy cortas exigiendo que las reseñas tengan una longitud superior a este umbral:
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
{'train': 138514, 'test': 46108}
Como puedes ver, esto ha eliminado alrededor del 15% de las reseñas de nuestros conjuntos de entrenamiento y prueba originales.
[!TIP] ✏️ ¡Pruébalo! Usa la función
Dataset.sort()para inspeccionar las reseñas con el mayor número de palabras. Consulta la documentación para ver qué argumento necesitas usar para ordenar las reseñas por longitud en orden descendente.
Lo último que debemos abordar es la presencia de códigos de caracteres HTML en nuestras reseñas. Podemos usar el módulo html de Python para decodificar estos caracteres, así:
text = "I'm a transformer called BERT"
html.unescape(text)
"I'm a transformer called BERT"
Usaremos Dataset.map() para decodificar todos los caracteres HTML en nuestro corpus:
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
Como puedes ver, el método Dataset.map() es bastante útil para procesar datos, ¡y ni siquiera hemos arañado la superficie de todo lo que puede hacer!
Los superpoderes del método map()[[the-map-methods-superpowers]]
El método Dataset.map() toma un argumento batched que, si se establece en True, hace que envíe un lote de ejemplos a la función de mapeo a la vez (el tamaño del lote es configurable pero por defecto es 1,000). Por ejemplo, la función de mapeo anterior que decodificaba todo el HTML tardó un poco en ejecutarse (puedes leer el tiempo transcurrido en las barras de progreso). Podemos acelerar esto procesando varios elementos al mismo tiempo usando una comprensión de lista.
Cuando especificas batched=True, la función recibe un diccionario con los campos del conjunto de datos, pero cada valor es ahora una lista de valores, y no solo un único valor. El valor de retorno de Dataset.map() debe ser el mismo: un diccionario con los campos que queremos actualizar o añadir a nuestro conjunto de datos, y una lista de valores. Por ejemplo, aquí hay otra forma de decodificar todos los caracteres HTML, pero usando batched=True:
new_drug_dataset = drug_dataset.map(
lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
Si estás ejecutando este código en un notebook, verás que este comando se ejecuta mucho más rápido que el anterior. Y no es porque nuestras reseñas ya hayan sido decodificadas de HTML; si vuelves a ejecutar la instrucción de la sección anterior (sin batched=True), tardará la misma cantidad de tiempo que antes. Esto se debe a que las comprensiones de lista suelen ser más rápidas que ejecutar el mismo código en un bucle for, y también obtenemos algo de rendimiento al acceder a muchos elementos al mismo tiempo en lugar de uno por uno.
Usar Dataset.map() con batched=True será esencial para desbloquear la velocidad de los tokenizadores "rápidos" que encontraremos en el Capítulo 6, que pueden tokenizar rápidamente grandes listas de textos. Por ejemplo, para tokenizar todas las reseñas de medicamentos con un tokenizador rápido, podríamos usar una función como esta:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["review"], truncation=True)
Como viste en el Capítulo 3, podemos pasar uno o varios ejemplos al tokenizador, por lo que podemos usar esta función con o sin batched=True. Aprovechemos esta oportunidad para comparar el rendimiento de las diferentes opciones. En un notebook, puedes cronometrar una instrucción de una sola línea añadiendo %time antes de la línea de código que deseas medir:
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
También puedes cronometrar una celda completa poniendo %%time al principio de la celda. En el hardware en el que ejecutamos esto, mostró 10.8s para esta instrucción (es el número escrito después de "Wall time").
[!TIP] ✏️ ¡Pruébalo! Ejecuta la misma instrucción con y sin
batched=True, luego inténtalo con un tokenizador lento (añadeuse_fast=Falseen el métodoAutoTokenizer.from_pretrained()) para que puedas ver qué números obtienes en tu hardware.
Aquí están los resultados que obtuvimos con y sin procesamiento por lotes, con un tokenizador rápido y uno lento:
| Opciones | Tokenizador rápido | Tokenizador lento |
|---|---|---|
batched=True |
10.8s | 4min41s |
batched=False |
59.2s | 5min3s |
Esto significa que usar un tokenizador rápido con la opción batched=True es 30 veces más rápido que su contraparte lenta sin procesamiento por lotes, ¡esto es realmente asombroso! Esa es la razón principal por la que los tokenizadores rápidos son los predeterminados al usar AutoTokenizer (y por qué se les llama "rápidos"). Logran tal aceleración porque, detrás de escena, el código de tokenización se ejecuta en Rust, que es un lenguaje que facilita la paralelización de la ejecución del código.
La paralelización también es la razón del aumento de velocidad de casi 6x que logra el tokenizador rápido con el procesamiento por lotes: no puedes paralelizar una sola operación de tokenización, pero cuando quieres tokenizar muchos textos al mismo tiempo, puedes simplemente dividir la ejecución entre varios procesos, cada uno responsable de sus propios textos.
Dataset.map() también tiene algunas capacidades de paralelización propias. Dado que no están respaldadas por Rust, no permitirán que un tokenizador lento alcance a uno rápido, pero aún pueden ser útiles (especialmente si estás usando un tokenizador que no tiene una versión rápida). Para habilitar el multiprocesamiento, usa el argumento num_proc y especifica el número de procesos a usar en tu llamada a Dataset.map():
slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False)
def slow_tokenize_function(examples):
return slow_tokenizer(examples["review"], truncation=True)
tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8)
Puedes experimentar un poco con el tiempo para determinar el número óptimo de procesos a usar; en nuestro caso, 8 pareció producir la mejor ganancia de velocidad. Aquí están los números que obtuvimos con y sin multiprocesamiento:
| Opciones | Tokenizador rápido | Tokenizador lento |
|---|---|---|
batched=True |
10.8s | 4min41s |
batched=False |
59.2s | 5min3s |
batched=True, num_proc=8 |
6.52s | 41.3s |
batched=False, num_proc=8 |
9.49s | 45.2s |
Esos son resultados mucho más razonables para el tokenizador lento, pero el rendimiento del tokenizador rápido también mejoró sustancialmente. Sin embargo, ten en cuenta que no siempre será así; para valores de num_proc distintos de 8, nuestras pruebas mostraron que era más rápido usar batched=True sin esa opción. En general, no recomendamos usar el multiprocesamiento de Python para tokenizadores rápidos con batched=True.
[!TIP] Usar
num_procpara acelerar tu procesamiento suele ser una gran idea, siempre y cuando la función que estés usando no esté haciendo ya algún tipo de multiprocesamiento por sí misma.
Toda esta funcionalidad condensada en un solo método ya es bastante asombrosa, ¡pero hay más! Con Dataset.map() y batched=True puedes cambiar el número de elementos en tu conjunto de datos. Esto es súper útil en muchas situaciones en las que quieres crear varias características de entrenamiento a partir de un ejemplo, y necesitaremos hacer esto como parte del preprocesamiento para varias de las tareas de PNL que emprenderemos en el Capítulo 7.
[!TIP] 💡 En aprendizaje automático, un ejemplo se define generalmente como el conjunto de características que alimentamos al modelo. En algunos contextos, estas características serán el conjunto de columnas en un
Dataset, pero en otros (como aquí y para la respuesta a preguntas), se pueden extraer múltiples características de un solo ejemplo y pertenecer a una sola columna.
¡Veamos cómo funciona! Aquí tokenizaremos nuestros ejemplos y los truncaremos a una longitud máxima de 128, pero le pediremos al tokenizador que devuelva todos los fragmentos de los textos en lugar de solo el primero. Esto se puede hacer con return_overflowing_tokens=True:
def tokenize_and_split(examples):
return tokenizer(
examples["review"],
truncation=True,
max_length=128,
return_overflowing_tokens=True,
)
Probemos esto en un ejemplo antes de usar Dataset.map() en todo el conjunto de datos:
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
[128, 49]
Así, nuestro primer ejemplo en el conjunto de entrenamiento se convirtió en dos características porque se tokenizó a más del número máximo de tokens que especificamos: la primera de longitud 128 y la segunda de longitud 49. ¡Ahora hagamos esto para todos los elementos del conjunto de datos!
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000
¡Oh no! ¡Eso no funcionó! ¿Por qué no? Mirar el mensaje de error nos dará una pista: hay una falta de coincidencia en las longitudes de una de las columnas, una de longitud 1,463 y la otra de longitud 1,000. Si has consultado la documentación de Dataset.map(), quizás recuerdes que es el número de muestras pasadas a la función que estamos mapeando; aquí esos 1,000 ejemplos dieron 1,463 nuevas características, lo que resultó en un error de forma.
El problema es que estamos intentando mezclar dos conjuntos de datos diferentes de distintos tamaños: las columnas drug_dataset tendrán un cierto número de ejemplos (los 1,000 de nuestro error), pero el tokenized_dataset que estamos construyendo tendrá más (los 1,463 del mensaje de error; es más de 1,000 porque estamos tokenizando reseñas largas en más de un ejemplo usando return_overflowing_tokens=True). Eso no funciona para un Dataset, así que necesitamos eliminar las columnas del conjunto de datos antiguo o hacer que tengan el mismo tamaño que en el nuevo conjunto de datos. Podemos hacer lo primero con el argumento remove_columns:
tokenized_dataset = drug_dataset.map(
tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)
Ahora esto funciona sin error. Podemos verificar que nuestro nuevo conjunto de datos tiene muchos más elementos que el conjunto de datos original comparando las longitudes:
len(tokenized_dataset["train"]), len(drug_dataset["train"])
(206772, 138514)
Mencionamos que también podemos lidiar con el problema de la longitud no coincidente haciendo que las columnas antiguas tengan el mismo tamaño que las nuevas. Para hacer esto, necesitaremos el campo overflow_to_sample_mapping que el tokenizador devuelve cuando configuramos return_overflowing_tokens=True. Nos da un mapeo de un nuevo índice de característica al índice de la muestra de la que se originó. Usando esto, podemos asociar cada clave presente en nuestro conjunto de datos original con una lista de valores del tamaño correcto repitiendo los valores de cada ejemplo tantas veces como genere nuevas características:
def tokenize_and_split(examples):
result = tokenizer(
examples["review"],
truncation=True,
max_length=128,
return_overflowing_tokens=True,
)
# Extract mapping between new and old indices
sample_map = result.pop("overflow_to_sample_mapping")
for key, values in examples.items():
result[key] = [values[i] for i in sample_map]
return result
Podemos ver que funciona con Dataset.map() sin que tengamos que eliminar las columnas antiguas:
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
tokenized_dataset
DatasetDict({
train: Dataset({
features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
num_rows: 206772
})
test: Dataset({
features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
num_rows: 68876
})
})
Obtenemos el mismo número de características de entrenamiento que antes, pero aquí hemos conservado todos los campos antiguos. Si los necesitas para algún posprocesamiento después de aplicar tu modelo, quizás quieras usar este enfoque.
Ahora has visto cómo se puede usar 🤗 Datasets para preprocesar un conjunto de datos de varias maneras. Aunque las funciones de procesamiento de 🤗 Datasets cubrirán la mayoría de tus necesidades de entrenamiento de modelos,
puede haber momentos en los que necesites cambiar a Pandas para acceder a características más potentes, como DataFrame.groupby() o APIs de alto nivel para visualización. Afortunadamente, 🤗 Datasets está diseñado para ser interoperable con bibliotecas como Pandas, NumPy, PyTorch, TensorFlow y JAX. Veamos cómo funciona esto.
De Dataset a DataFrame y viceversa[[from-datasets-to-dataframes-and-back]]
Para permitir la conversión entre varias bibliotecas de terceros, 🤗 Datasets proporciona una función Dataset.set_format(). Esta función solo cambia el formato de salida del conjunto de datos, por lo que puedes cambiar fácilmente a otro formato sin afectar el formato de datos subyacente, que es Apache Arrow. El formato se realiza in situ. Para demostrarlo, convirtamos nuestro conjunto de datos a Pandas:
drug_dataset.set_format("pandas")
Ahora, cuando accedemos a los elementos del conjunto de datos, obtenemos un pandas.DataFrame en lugar de un diccionario:
drug_dataset["train"][:3]
| patient_id | drugName | condition | review | rating | date | usefulCount | review_length | |
|---|---|---|---|---|---|---|---|---|
| 0 | 95260 | Guanfacine | adhd | "My son is halfway through his fourth week of Intuniv..." | 8.0 | April 27, 2010 | 192 | 141 |
| 1 | 92703 | Lybrel | birth control | "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..." | 5.0 | December 14, 2009 | 17 | 134 |
| 2 | 138000 | Ortho Evra | birth control | "This is my first time using any form of birth control..." | 8.0 | November 3, 2015 | 10 | 89 |
Creemos un pandas.DataFrame para todo el conjunto de entrenamiento seleccionando todos los elementos de drug_dataset["train"]:
train_df = drug_dataset["train"][:]
[!TIP] 🚨 Bajo el capó,
Dataset.set_format()cambia el formato de retorno para el método dunder__getitem__()del conjunto de datos. Esto significa que cuando queremos crear un nuevo objeto comotrain_dfa partir de unDataseten el formato"pandas", necesitamos dividir todo el conjunto de datos para obtener unpandas.DataFrame. Puedes verificar por ti mismo que el tipo dedrug_dataset["train"]esDataset, independientemente del formato de salida.
Desde aquí podemos usar toda la funcionalidad de Pandas que queramos. Por ejemplo, podemos hacer encadenamientos sofisticados para calcular la distribución de clases entre las entradas condition:
frequencies = (
train_df["condition"]
.value_counts()
.to_frame()
.reset_index()
.rename(columns={"index": "condition", "count": "frequency"})
)
frequencies.head()
| condition | frequency | |
|---|---|---|
| 0 | birth control | 27655 |
| 1 | depression | 8023 |
| 2 | acne | 5209 |
| 3 | anxiety | 4991 |
| 4 | pain | 4744 |