☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Mira los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
- Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
- Consigue la [**oficial PEASS & HackTricks swag**](https://peass.creator-spring.com)
- **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
- **Comparte tus trucos de hacking enviando PRs al [repositorio de hacktricks](https://github.com/carlospolop/hacktricks) y al [repositorio de hacktricks-cloud](https://github.com/carlospolop/hacktricks-cloud)**.
# Tipos básicos de datos posibles
Los datos pueden ser **continuos** (con **infinitos** valores) o **categóricos** (nominales) donde la cantidad de valores posibles es **limitada**.
## Tipos categóricos
### Binario
Solo hay **2 valores posibles**: 1 o 0. En caso de que en un conjunto de datos los valores estén en formato de cadena (por ejemplo, "Verdadero" y "Falso"), se asignan números a esos valores con:
```python
dataset["column2"] = dataset.column2.map({"T": 1, "F": 0})
```
### **Ordinal**
Los **valores siguen un orden**, como en: 1er lugar, 2do lugar... Si las categorías son cadenas de texto (como: "principiante", "amateur", "profesional", "experto") se pueden asignar números a cada una de ellas como vimos en el caso binario.
```python
column2_mapping = {'starter':0,'amateur':1,'professional':2,'expert':3}
dataset['column2'] = dataset.column2.map(column2_mapping)
```
* Para las columnas **alfabéticas** puedes ordenarlas más fácilmente:
```python
# First get all the uniq values alphabetically sorted
possible_values_sorted = dataset.column2.sort_values().unique().tolist()
# Assign each one a value
possible_values_mapping = {value:idx for idx,value in enumerate(possible_values_sorted)}
dataset['column2'] = dataset.column2.map(possible_values_mapping)
```
### **Cíclico**
Se parece a un valor ordinal porque hay un orden, pero no significa que uno sea más grande que el otro. Además, la distancia entre ellos depende de la dirección en la que se esté contando. Ejemplo: los días de la semana, el domingo no es "más grande" que el lunes.
* Hay diferentes formas de codificar características cíclicas, algunas pueden funcionar solo con algunos algoritmos. En general, se puede utilizar la codificación de variables ficticias.
```python
column2_dummies = pd.get_dummies(dataset.column2, drop_first=True)
dataset_joined = pd.concat([dataset[['column2']], column2_dummies], axis=1)
```
### **Fechas**
Las fechas son **variables continuas**. Pueden ser vistas como **cíclicas** (porque se repiten) o como variables **ordinales** (porque un tiempo es mayor que otro anterior).
* Usualmente las fechas son usadas como **índice**.
```python
# Transform dates to datetime
dataset["column_date"] = pd.to_datetime(dataset.column_date)
# Make the date feature the index
dataset.set_index('column_date', inplace=True)
print(dataset.head())
# Sum usage column per day
daily_sum = dataset.groupby(df_daily_usage.index.date).agg({'usage':['sum']})
# Flatten and rename usage column
daily_sum.columns = daily_sum.columns.get_level_values(0)
daily_sum.columns = ['daily_usage']
print(daily_sum.head())
# Fill days with 0 usage
idx = pd.date_range('2020-01-01', '2020-12-31')
daily_sum.index = pd.DatetimeIndex(daily_sum.index)
df_filled = daily_sum.reindex(idx, fill_value=0) # Fill missing values
# Get day of the week, Monday=0, Sunday=6, and week days names
dataset['DoW'] = dataset.transaction_date.dt.dayofweek
# do the same in a different way
dataset['weekday'] = dataset.transaction_date.dt.weekday
# get day names
dataset['day_name'] = dataset.transaction_date.apply(lambda x: x.day_name())
```
### Multi-categoría/nominal
**Más de 2 categorías** sin un orden relacionado. Use `dataset.describe(include='all')` para obtener información sobre las categorías de cada característica.
* Una **cadena de referencia** es una **columna que identifica un ejemplo** (como el nombre de una persona). Esto puede estar duplicado (porque 2 personas pueden tener el mismo nombre), pero la mayoría será único. Estos datos son **inútiles y deben eliminarse**.
* Una **columna clave** se utiliza para **vincular datos entre tablas**. En este caso, los elementos son únicos. Estos datos son **inútiles y deben eliminarse**.
Para **codificar columnas de múltiples categorías en números** (para que el algoritmo de ML los entienda), se utiliza la **codificación de dummies** (y **no la codificación one-hot** porque **no evita la multicolinealidad perfecta**).
Puede obtener una **columna de múltiples categorías codificada one-hot** con `pd.get_dummies(dataset.column1)`. Esto transformará todas las clases en características binarias, por lo que creará **una nueva columna por cada clase posible** y asignará 1 **valor verdadero a una columna**, y el resto será falso.
Puede obtener una **columna de múltiples categorías codificada en dummies** con `pd.get_dummies(dataset.column1, drop_first=True)`. Esto transformará todas las clases en características binarias, por lo que creará **una nueva columna por cada clase posible menos una** ya que **las últimas 2 columnas se reflejarán como "1" o "0" en la última columna binaria creada**. Esto evitará la multicolinealidad perfecta, reduciendo las relaciones entre columnas.
# Colinealidad/Multicolinealidad
La colinealidad aparece cuando **2 características están relacionadas entre sí**. La multicolinealidad aparece cuando hay más de 2.
En ML **quieres que tus características estén relacionadas con los posibles resultados, pero no quieres que estén relacionadas entre sí**. Es por eso que la **codificación de dummies mezcla las últimas dos columnas** de eso y **es mejor que la codificación one-hot** que no lo hace, creando una clara relación entre todas las nuevas características de la columna de múltiples categorías.
VIF es el **Factor de Inflación de la Varianza** que **mide la multicolinealidad de las características**. Un valor **superior a 5 significa que una de las dos o más características colineales debe eliminarse**.
```python
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
#dummies_encoded = pd.get_dummies(dataset.column1, drop_first=True)
onehot_encoded = pd.get_dummies(dataset.column1)
X = add_constant(onehot_encoded) # Add previously one-hot encoded data
print(pd.Series([variance_inflation_factor(X.values,i) for i in range(X.shape[1])], index=X.columns))
```
# Desequilibrio Categórico
Esto ocurre cuando **no hay la misma cantidad de cada categoría** en los datos de entrenamiento.
```python
# Get statistic of the features
print(dataset.describe(include='all'))
# Get an overview of the features
print(dataset.info())
# Get imbalance information of the target column
print(dataset.target_column.value_counts())
```
En un desequilibrio siempre hay una **clase o clases mayoritarias** y una **clase o clases minoritarias**.
Hay 2 formas principales de solucionar este problema:
* **Submuestreo**: Eliminar datos seleccionados al azar de la clase mayoritaria para que tenga el mismo número de muestras que la clase minoritaria.
```python
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUserSampler(random_state=1337)
X = dataset[['column1', 'column2', 'column3']].copy()
y = dataset.target_column
X_under, y_under = rus.fit_resample(X,y)
print(y_under.value_counts()) #Confirm data isn't imbalanced anymore
```
* **Sobremuestreo**: Generar más datos para la clase minoritaria hasta que tenga tantas muestras como la clase mayoritaria.
```python
from imblearn.under_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=1337)
X = dataset[['column1', 'column2', 'column3']].copy()
y = dataset.target_column
X_over, y_over = ros.fit_resample(X,y)
print(y_over.value_counts()) #Confirm data isn't imbalanced anymore
```
Puedes usar el argumento **`sampling_strategy`** para indicar el **porcentaje** que deseas **submuestrear o sobremuestrear** (**por defecto es 1 (100%)** lo que significa igualar el número de clases minoritarias con las clases mayoritarias).
{% hint style="info" %}
El submuestreo o sobremuestreo no son perfectos, si obtienes estadísticas (con `.describe()`) de los datos sobre/submuestreados y los comparas con los originales, verás **que han cambiado**. Por lo tanto, el sobremuestreo y el submuestreo modifican los datos de entrenamiento.
{% endhint %}
## Sobremuestreo SMOTE
**SMOTE** es generalmente una **forma más confiable de sobremuestrear los datos**.
```python
from imblearn.over_sampling import SMOTE
# Form SMOTE the target_column need to be numeric, map it if necessary
smote = SMOTE(random_state=1337)
X_smote, y_smote = smote.fit_resample(dataset[['column1', 'column2', 'column3']], dataset.target_column)
dataset_smote = pd.DataFrame(X_smote, columns=['column1', 'column2', 'column3'])
dataset['target_column'] = y_smote
print(y_smote.value_counts()) #Confirm data isn't imbalanced anymore
```
# Categorías de ocurrencia rara
Imagina un conjunto de datos donde una de las clases objetivo ocurre muy pocas veces.
Esto es similar al desequilibrio de categorías de la sección anterior, pero la categoría de ocurrencia rara ocurre incluso menos que la "clase minoritaria" en ese caso. Los métodos de **sobremuestreo** y **submuestreo** **brutos** también podrían usarse aquí, pero generalmente esas técnicas **no darán resultados realmente buenos**.
## Pesos
En algunos algoritmos es posible **modificar los pesos de los datos objetivo** para que algunos de ellos tengan por defecto más importancia al generar el modelo.
```python
weights = {0: 10 1:1} #Assign weight 10 to False and 1 to True
model = LogisticRegression(class_weight=weights)
```
Puedes **mezclar los pesos con técnicas de sobremuestreo/submuestreo** para intentar mejorar los resultados.
## PCA - Análisis de Componentes Principales
Es un método que ayuda a reducir la dimensionalidad de los datos. Va a **combinar diferentes características** para **reducir la cantidad** de ellas generando **características más útiles** (_se necesita menos cómputo_).
Las características resultantes no son comprensibles por los humanos, por lo que también **anonimiza los datos**.
# Categorías de etiquetas incongruentes
Los datos pueden tener errores por transformaciones fallidas o simplemente por errores humanos al escribir los datos.
Por lo tanto, es posible encontrar la **misma etiqueta con errores ortográficos**, diferentes **mayúsculas**, **abreviaturas** como: _BLUE, Blue, b, bule_. Necesitas corregir estos errores de etiqueta dentro de los datos antes de entrenar el modelo.
Puedes solucionar estos problemas convirtiendo todo en minúsculas y asignando etiquetas mal escritas a las correctas.
Es muy importante comprobar que **todos los datos que tienes están etiquetados correctamente**, porque por ejemplo, un error de ortografía en los datos, al codificar las clases, generará una nueva columna en las características finales con **consecuencias negativas para el modelo final**. Este ejemplo se puede detectar muy fácilmente codificando en caliente una columna y comprobando los nombres de las columnas creadas.
# Datos faltantes
Puede faltar algún dato del estudio.
Puede suceder que falte algún dato completamente al azar por algún error. Este tipo de dato está **Completamente Faltante al Azar** (**MCAR**).
Podría ser que falte algún dato al azar, pero hay algo que hace que algunos detalles específicos sean más probables de faltar, por ejemplo, los hombres suelen decir su edad con más frecuencia que las mujeres. Esto se llama **Faltante al Azar** (**MAR**).
Finalmente, podría haber datos **Faltantes No al Azar** (**MNAR**). El valor de los datos está directamente relacionado con la probabilidad de tener los datos. Por ejemplo, si quieres medir algo vergonzoso, cuanto más vergonzoso sea alguien, menos probable es que lo comparta.
Las **dos primeras categorías** de datos faltantes se pueden **ignorar**. Pero la **tercera** requiere considerar **sólo porciones de los datos** que no estén afectadas o intentar **modelar los datos faltantes de alguna manera**.
Una forma de averiguar sobre los datos faltantes es usar la función `.info()`, ya que indicará el **número de filas pero también el número de valores por categoría**. Si alguna categoría tiene menos valores que el número de filas, entonces faltan algunos datos:
```bash
# Get info of the dataset
dataset.info()
# Drop all rows where some value is missing
dataset.dropna(how='any', axis=0).info()
```
Generalmente se recomienda que si una característica **falta en más del 20%** del conjunto de datos, la **columna debe ser eliminada:**
```bash
# Remove column
dataset.drop('Column_name', axis='columns', inplace=True)
dataset.info()
```
{% hint style="info" %}
Ten en cuenta que **no todos los valores faltantes están ausentes en el conjunto de datos**. Es posible que los valores faltantes hayan sido reemplazados por "Desconocido", "n/a", "", -1, 0... Debes verificar el conjunto de datos (usando `dataset.column`_`name.value`_`counts(dropna=False)` para verificar los posibles valores).
{% endhint %}
Si falta algún dato en el conjunto de datos (si no es demasiado), debes encontrar la **categoría de los datos faltantes**. Para ello, básicamente necesitas saber si los **datos faltantes están al azar o no**, y para ello necesitas encontrar si los **datos faltantes estaban correlacionados con otros datos** del conjunto de datos.
Para encontrar si un valor faltante está correlacionado con otra columna, puedes crear una nueva columna que ponga 1 y 0 si los datos faltan o no, y luego calcular la correlación entre ellos:
```bash
# The closer it's to 1 or -1 the more correlated the data is
# Note that columns are always perfectly correlated with themselves.
dataset[['column_name', 'cloumn_missing_data']].corr()
```
Si decides ignorar los datos faltantes, aún necesitas decidir qué hacer con ellos: puedes **eliminar las filas** con datos faltantes (los datos de entrenamiento para el modelo serán más pequeños), puedes **eliminar completamente la característica**, o puedes **modelarla**.
Debes **verificar la correlación entre la característica faltante y la columna objetivo** para ver qué tan importante es esa característica para el objetivo, si es realmente **pequeña**, puedes **eliminarla o llenarla**.
Para llenar datos faltantes **continuos**, puedes usar: la **media**, la **mediana** o usar un **algoritmo de imputación**. El algoritmo de imputación puede intentar usar otras características para encontrar un valor para la característica faltante:
```python
from sklearn.impute import KNNImputer
X = dataset[['column1', 'column2', 'column3']]
y = dataset.column_target
# Create the imputer that will fill the data
imputer = KNNImputer(n_neightbors=2, weights='uniform')
X_imp = imputer.fit_transform(X)
# Check new data
dataset_imp = pd.DataFrame(X_imp)
dataset.columns = ['column1', 'column2', 'column3']
dataset.iloc[10:20] # Get some indexes that contained empty data before
```
Para rellenar datos categóricos, primero debes pensar si hay alguna razón por la cual los valores estén faltando. Si es por **elección de los usuarios** (no quisieron proporcionar los datos), tal vez puedas **crear una nueva categoría** que lo indique. Si es debido a un error humano, puedes **eliminar las filas** o la **característica** (verifica los pasos mencionados anteriormente) o **rellenarla con la moda, la categoría más utilizada** (no recomendado).
# Combinando Características
Si encuentras **dos características** que están **correlacionadas** entre sí, generalmente deberías **eliminar** una de ellas (la que está menos correlacionada con el objetivo), pero también podrías intentar **combinarlas y crear una nueva característica**.
```python
# Create a new feautr combining feature1 and feature2
dataset['new_feature'] = dataset.column1/dataset.column2
# Check correlation with target column
dataset[['new_feature', 'column1', 'column2', 'target']].corr()['target'][:]
# Check for collinearity of the 2 features and the new one
X = add_constant(dataset[['column1', 'column2', 'target']])
# Calculate VIF
pd.Series([variance_inflation_factor(X.values, i) for i in range(X.shape[1])], index=X.columns)
```
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Revisa los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
- Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
- Obtén el [**swag oficial de PEASS y HackTricks**](https://peass.creator-spring.com)
- **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) **grupo de Discord** o al [**grupo de telegram**](https://t.me/peass) o **sígueme en** **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
- **Comparte tus trucos de hacking enviando PRs al [repositorio de hacktricks](https://github.com/carlospolop/hacktricks) y al [repositorio de hacktricks-cloud](https://github.com/carlospolop/hacktricks-cloud)**.