{"id":573205,"date":"2025-06-05T03:38:54","date_gmt":"2025-06-05T02:38:54","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=573205"},"modified":"2025-06-05T03:39:01","modified_gmt":"2025-06-05T02:39:01","slug":"deteccion-de-anomalias-en-series-temporales","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/es\/pycharm\/2025\/06\/deteccion-de-anomalias-en-series-temporales\/","title":{"rendered":"Detecci\u00f3n de anomal\u00edas en series temporales"},"content":{"rendered":"<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-573221\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/PC-social-BlogFeatured-1280x720-2x-5.png\" alt=\"\" width=\"2559\" height=\"1439\" \/><\/figure>\n<p>\u00bfC\u00f3mo identificar patrones inusuales en los datos que puedan revelar problemas cr\u00edticos u oportunidades ocultas? La <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\">detecci\u00f3n de anomal\u00edas<\/a> ayuda a identificar los datos que se desv\u00edan significativamente de la norma. Los datos de series temporales, que constan de datos recogidos a lo largo del tiempo, suelen incluir tendencias y patrones estacionales. Las anomal\u00edas en los datos de series temporales se producen cuando se interrumpen estos patrones, lo que convierte a la detecci\u00f3n de anomal\u00edas en una valiosa herramienta en sectores como las ventas, las finanzas, la fabricaci\u00f3n y la atenci\u00f3n sanitaria.<\/p>\n<p>Dado que los datos de series temporales tienen caracter\u00edsticas \u00fanicas como la estacionalidad y las tendencias, se requieren m\u00e9todos especializados para detectar anomal\u00edas de forma eficaz. En este art\u00edculo del blog, exploraremos algunos m\u00e9todos populares para la detecci\u00f3n de anomal\u00edas en series temporales, incluyendo la descomposici\u00f3n STL y la predicci\u00f3n LSTM, con ejemplos de c\u00f3digo detallados para ayudarle a empezar.<\/p>\n<h2 class=\"wp-block-heading\">Detecci\u00f3n de anomal\u00edas en series temporales en las empresas<\/h2>\n<p>Los datos de series temporales son esenciales para muchas empresas y servicios. Muchas empresas registran los datos a lo largo del tiempo con marcas de tiempo, lo que permite analizar los cambios y comparar los datos a lo largo del tiempo. Las series temporales son \u00fatiles cuando se compara una cantidad determinada a lo largo de un periodo determinado, como, por ejemplo, en una comparaci\u00f3n interanual en la que los datos presentan caracter\u00edsticas de estacionalidad.<\/p>\n<p><strong>Seguimiento de las ventas<\/strong><\/p>\n<p>Uno de los ejemplos m\u00e1s comunes de datos de series temporales con estacionalidad son los datos de ventas. Dado que muchas ventas se ven afectadas por las fiestas anuales y la \u00e9poca del a\u00f1o, es dif\u00edcil sacar conclusiones sobre los datos de ventas sin tener en cuenta la estacionalidad. Por ello, un m\u00e9todo habitual para analizar y detectar anomal\u00edas en los datos de ventas es la descomposici\u00f3n STL, que trataremos en detalle <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#stl-beehive\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#stl-beehive\">m\u00e1s adelante en este art\u00edculo del blog.<\/a><\/p>\n<p><strong>Finanzas<\/strong><\/p>\n<p>Los datos financieros, como las transacciones y los precios de las acciones, son ejemplos t\u00edpicos de datos de series temporales. En el sector financiero, analizar y detectar anomal\u00edas en estos datos es una pr\u00e1ctica habitual. Por ejemplo, los modelos de predicci\u00f3n de series temporales pueden utilizarse en el trading autom\u00e1tico. Utilizaremos una predicci\u00f3n de series temporales para identificar anomal\u00edas en los datos burs\u00e1tiles <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#lstm-stock\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#lstm-stock\">m\u00e1s adelante en este art\u00edculo del blog.<\/a><\/p>\n<p><strong>Fabricaci\u00f3n<\/strong><\/p>\n<p>Otro caso de uso de la detecci\u00f3n de anomal\u00edas en series temporales es la supervisi\u00f3n de defectos en las l\u00edneas de producci\u00f3n. Las m\u00e1quinas suelen supervisarse, lo que permite disponer de datos de series temporales. Es esencial poder notificar a la direcci\u00f3n los posibles fallos, y la detecci\u00f3n de anomal\u00edas desempe\u00f1a un papel clave.<\/p>\n<p><strong>Medicina y sanidad<\/strong><\/p>\n<p>En medicina y sanidad, se controlan las constantes vitales humanas y se pueden detectar anomal\u00edas. Esto ya es importante en la investigaci\u00f3n m\u00e9dica, pero es fundamental en el diagn\u00f3stico. Si un paciente de un hospital presenta anomal\u00edas en sus constantes vitales y no se le trata inmediatamente, los resultados pueden ser fatales.<\/p>\n<h2 class=\"wp-block-heading\">\u00bfPor qu\u00e9 es importante utilizar m\u00e9todos especiales para la detecci\u00f3n de anomal\u00edas en las series temporales?<\/h2>\n<p>Los datos de series temporales son especiales en el sentido de que a veces no pueden tratarse como otros tipos de datos. Por ejemplo, cuando aplicamos una divisi\u00f3n entrenamiento-prueba a datos de series temporales, a causa de la naturaleza secuencialmente relacionada de los datos, no podemos cambiarlos de orden. Esto tambi\u00e9n sucede cuando se aplican datos de series temporales a un modelo de aprendizaje profundo. Se suele utilizar una red neuronal recurrente (RNN) para tener en cuenta la relaci\u00f3n secuencial, y los datos de entrenamiento se introducen como ventanas temporales, que conservan la secuencia de acontecimientos en su interior.<\/p>\n<p>Los datos de series temporales tambi\u00e9n son especiales porque a menudo presentan estacionalidad y tendencias que no podemos ignorar. Esta estacionalidad puede manifestarse en un ciclo de 24 horas, un ciclo de 7 d\u00edas o un ciclo de 12 meses, por nombrar algunas posibilidades comunes. Las anomal\u00edas solo pueden determinarse despu\u00e9s de haber tenido en cuenta la estacionalidad y las tendencias, como ver\u00e1 en <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#stl-beehive\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#stl-beehive\">nuestro ejemplo a continuaci\u00f3n<\/a>.\u00a0<\/p>\n<h2 class=\"wp-block-heading\">M\u00e9todos utilizados para la detecci\u00f3n de anomal\u00edas en las series temporales<\/h2>\n<p>Dado que los datos de series temporales son especiales, existen m\u00e9todos espec\u00edficos para detectar anomal\u00edas en ellos. Dependiendo del tipo de datos, algunos de los m\u00e9todos y algoritmos que mencionamos en el anterior <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\">art\u00edculo del blog sobre detecci\u00f3n de anomal\u00edas<\/a> pueden utilizarse en datos de series temporales. Sin embargo, con esos m\u00e9todos, la detecci\u00f3n de anomal\u00edas puede no ser tan robusta como si se utilizaran los dise\u00f1ados espec\u00edficamente para datos de series temporales. En algunos casos, puede utilizarse una combinaci\u00f3n de m\u00e9todos de detecci\u00f3n para reconfirmar el resultado de la detecci\u00f3n y evitar falsos positivos o negativos.<\/p>\n<h3 class=\"wp-block-heading\">Descomposici\u00f3n STL<\/h3>\n<p>Una de las formas m\u00e1s populares de utilizar datos de series temporales que presentan estacionalidad es la descomposici\u00f3n STL: descomposici\u00f3n de la tendencia estacional mediante LOESS (regresi\u00f3n local). En este m\u00e9todo, una serie temporal se descompone utilizando una estimaci\u00f3n de la estacionalidad (con el periodo proporcionado o determinado mediante un algoritmo), una tendencia (estimada) y el residuo (el ruido de los datos). Una biblioteca <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/python.html\" target=\"_blank\" rel=\"noopener\">Python<\/a> que proporciona <a href=\"https:\/\/www.statsmodels.org\/stable\/examples\/notebooks\/generated\/stl_decomposition.html\" target=\"_blank\" rel=\"noopener\">herramientas de descomposici\u00f3n STL<\/a> es la biblioteca <a href=\"https:\/\/www.statsmodels.org\/stable\/index.html\" target=\"_blank\" rel=\"noopener\">statsmodels<\/a>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539263\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-70.png\" alt=\"Descomposici\u00f3n STL\" width=\"1600\" height=\"900\" \/><\/figure>\n<p>Se detecta una anomal\u00eda cuando el residuo supera un determinado umbral.\u00a0<\/p>\n<h3 id=\"stl-beehive\" class=\"wp-block-heading\">Uso de la descomposici\u00f3n STL en los datos de una colmena<\/h3>\n<p>En un <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-machine-learning\/\">art\u00edculo del blog<\/a> anterior, exploramos la detecci\u00f3n de anomal\u00edas en colmenas utilizando <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.svm.OneClassSVM.html\" target=\"_blank\" rel=\"noopener\">OneClassSVM<\/a> y los m\u00e9todos <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.ensemble.IsolationForest.html\" target=\"_blank\" rel=\"noopener\">IsolationForest<\/a>.\u00a0<\/p>\n<p>En este tutorial, analizaremos <a href=\"https:\/\/www.kaggle.com\/datasets\/vivovinco\/beehives\" target=\"_blank\" rel=\"noopener\">datos de colmenas<\/a> como una serie temporal utilizando la clase <code>STL<\/code> proporcionada por la biblioteca statsmodels. Para empezar, configure su entorno utilizando este archivo: <a href=\"https:\/\/github.com\/Cheukting\/anomaly-detection\/blob\/main\/requirements.txt\" target=\"_blank\" rel=\"noopener\">requierements.txt<\/a>.\u00a0<\/p>\n<h4 class=\"wp-block-heading\"><strong>1. Instale la biblioteca<\/strong><\/h4>\n<p>Dado que solo hemos estado utilizando el modelo proporcionado por Scikit-learn, necesitaremos instalar statsmodels desde PyPI. Esto es f\u00e1cil de hacer en <a href=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\/\" target=\"_blank\" rel=\"noreferrer noopener\">PyCharm<\/a>.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\/\" target=\"\" rel=\"noopener\">Comience con PyCharm Pro de forma gratuita<\/a><\/div>\n<\/div>\n<p>Vaya a la ventana <em><a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/installing-uninstalling-and-upgrading-packages.html\" target=\"_blank\" rel=\"noopener\">Python Packages<\/a><\/em> (elija el icono de la parte inferior izquierda del IDE) y escriba statsmodels en el cuadro de b\u00fasqueda.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539351\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-76.png\" alt=\"Statsmodels en PyCharm\" width=\"1600\" height=\"630\" \/><\/figure>\n<p>Puede ver toda la informaci\u00f3n sobre el paquete en la parte derecha. Para instalarlo, solo tiene que hacer clic en <em>Install package<\/em>.<\/p>\n<h4 class=\"wp-block-heading\"><strong>2. Cree un notebook de Jupyter<\/strong><\/h4>\n<p>Para investigar m\u00e1s a fondo el conjunto de datos, vamos a crear un <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/jupyter-notebook-support.html\" target=\"_blank\" rel=\"noopener\">notebook de Jupyter<\/a> para aprovechar las herramientas que proporciona el entorno de notebooks de Jupyter de PyCharm.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539362\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-77.png\" alt=\"Cree un  notebook de Jupyter en PyCharm\" width=\"1098\" height=\"410\" \/><\/figure>\n<p>Importaremos <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2024\/10\/data-exploration-with-pandas\/\">pandas<\/a> y cargaremos el archivo <code>.csv<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import pandas as pd\n\ndf = pd.read_csv('..\/data\/Hive17.csv', sep=\";\")\ndf = df.dropna()\ndf<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539310\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-73.png\" alt=\"Importar pandas en PyCharm\" width=\"1600\" height=\"930\" \/><\/figure>\n<h4 class=\"wp-block-heading\"><strong>3. Inspeccione los datos en forma de gr\u00e1ficos<\/strong><\/h4>\n<p>Ahora, podemos inspeccionar los datos en forma de gr\u00e1ficos. Aqu\u00ed nos gustar\u00eda ver la temperatura de la colmena 17 a lo largo del tiempo. Haga clic en <em>Chart view<\/em> en el inspector del marco de datos y, a continuaci\u00f3n, elija <em>T17<\/em> como eje y en los ajustes de la serie.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539299\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-2.gif\" alt=\"Inspeccionar los datos como gr\u00e1ficos en PyCharm\" width=\"720\" height=\"290\" \/><\/figure>\n<p>Cuando se expresa como una serie temporal, la temperatura presenta muchos altibajos. Esto indica un comportamiento peri\u00f3dico, probablemente debido al ciclo d\u00eda-noche, por lo que podemos asumir con seguridad que existe un periodo de 24 horas para la temperatura.\u00a0<\/p>\n<p>A continuaci\u00f3n, se observa una tendencia a la baja de la temperatura con el paso del tiempo. Si inspecciona la columna <em>DateTime<\/em>, podr\u00e1 ver que las fechas van de agosto a noviembre. Dado que la <a href=\"https:\/\/www.kaggle.com\/datasets\/vivovinco\/beehives\/data\" target=\"_blank\" rel=\"noopener\">p\u00e1gina Kaggle del conjunto de datos<\/a> indica que los datos se recopilaron en Turqu\u00eda, la transici\u00f3n del verano al oto\u00f1o explica nuestra observaci\u00f3n de que la temperatura desciende con el tiempo.<\/p>\n<h4 class=\"wp-block-heading\"><strong>4. Descomposici\u00f3n de series temporales<\/strong><\/h4>\n<p>Para comprender las series temporales y detectar anomal\u00edas, realizaremos una descomposici\u00f3n STL, importando la clase <code>STL<\/code> de statsmodels y ajust\u00e1ndola con nuestros datos de temperatura.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from statsmodels.tsa.seasonal import STL\n\nstl = STL(df[\"T17\"], period=24, robust=True) \nresult = stl.fit()<\/pre>\n<p>Tendremos que indicar un periodo para que la descomposici\u00f3n funcione. Como hemos mencionado antes, es seguro asumir un ciclo de 24 horas.<\/p>\n<p>Seg\u00fan la documentaci\u00f3n, <code>STL<\/code> descompone una serie temporal en tres componentes: tendencia, estacional y residual. Para obtener una visi\u00f3n m\u00e1s clara del resultado descompuesto, podemos utilizar el m\u00e9todo incorporado <code>plot<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">result.plot()<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539541\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/Time-series-decomposition.png\" alt=\"Descomposici\u00f3n de series temporales\" width=\"1600\" height=\"1100\" \/><\/figure>\n<p>Puede ver que los gr\u00e1ficos <em>Trend<\/em> y <em>Season<\/em> parecen alinearse con nuestras suposiciones anteriores. Sin embargo, nos interesa el gr\u00e1fico residual de la parte inferior, que es la serie original sin la tendencia ni los cambios estacionales. Cualquier valor extremadamente alto o bajo en el residuo indica una anomal\u00eda.<\/p>\n<h4 id=\"anomaly-threshold\" class=\"wp-block-heading\"><strong>5. Umbral de anomal\u00eda<\/strong><\/h4>\n<p>A continuaci\u00f3n, nos gustar\u00eda determinar qu\u00e9 valores del residuo consideraremos an\u00f3malos. Para ello, podemos observar el histograma del residuo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">result.resid.plot.hist()<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539375\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-78.png\" alt=\"Umbral de anomal\u00edas en PyCharm\" width=\"1328\" height=\"1048\" \/><\/figure>\n<p>Esta puede considerarse una distribuci\u00f3n normal en torno a 0, con una larga cola por encima de 5 y por debajo de -5, por lo que fijaremos el umbral en 5.<\/p>\n<p>Para mostrar las anomal\u00edas en la serie temporal original, podemos colorearlas todas de rojo en el gr\u00e1fico de esta forma:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import matplotlib.pyplot as plt\n\nthreshold = 5\nanomalies_filter = result.resid.apply(lambda x: True if abs(x) &gt; threshold else False)\nanomalies = df[\"T17\"][anomalies_filter]\n\nplt.figure(figsize=(14, 8))\nplt.scatter(x=anomalies.index, y=anomalies, color=\"red\", label=\"anomalies\")\nplt.plot(df.index, df['T17'], color='blue')\nplt.title('Temperatures in Hive 17')\nplt.xlabel('Hours')\nplt.ylabel('Temperature')\nplt.legend()\nplt.show()<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539386\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-79.png\" alt=\"Anomal\u00edas en la serie temporal original en PyCharm\" width=\"1600\" height=\"976\" \/><\/figure>\n<p>Sin la descomposici\u00f3n STL, es muy dif\u00edcil identificar estas anomal\u00edas en una serie temporal formada por periodos y tendencias.<\/p>\n<h3 class=\"wp-block-heading\">Predicci\u00f3n LSTM<\/h3>\n<p>Otra forma de detectar anomal\u00edas en los datos de series temporales es realizar una predicci\u00f3n de las series temporales sobre las series utilizando m\u00e9todos de aprendizaje profundo para estimar el resultado de los puntos de datos. Si una estimaci\u00f3n es muy diferente del punto de datos real, podr\u00eda ser un signo de datos an\u00f3malos.<\/p>\n<p>Uno de los algoritmos de aprendizaje profundo m\u00e1s populares para realizar la predicci\u00f3n de datos secuenciales es el modelo de memoria a largo plazo (LSTM), que es un tipo de red neuronal recurrente (RNN). El modelo LSTM tiene puertas de entrada, olvido y salida, que son matrices num\u00e9ricas. Esto garantiza que la informaci\u00f3n importante se transmita en la siguiente iteraci\u00f3n de los datos.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539580\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/LSTM-memory-cell.png\" alt=\"Celda de LSTM\" width=\"1600\" height=\"900\" \/><\/figure>\n<p>Dado que los datos de series temporales son datos secuenciales \u2014lo que significa que el orden de los puntos de datos es secuencial y no deben modificarse\u2014 el modelo LSTM es un modelo de aprendizaje profundo eficaz para predecir el resultado en un momento determinado. Esta predicci\u00f3n puede compararse con los datos reales y puede fijarse un umbral para determinar si los datos reales son una anomal\u00eda.<\/p>\n<h3 id=\"lstm-stock\" class=\"wp-block-heading\">Uso de la predicci\u00f3n LSTM en las cotizaciones burs\u00e1tiles<\/h3>\n<p>Ahora vamos a iniciar un nuevo proyecto Jupyter para detectar cualquier anomal\u00eda en el precio de las acciones de Apple durante los \u00faltimos 5 a\u00f1os. El <a href=\"https:\/\/www.nasdaq.com\/market-activity\/stocks\/aapl\/historical?page=1&amp;rows_per_page=25&amp;timeline=y5\" target=\"_blank\" rel=\"noopener\">conjunto de datos sobre cotizaciones burs\u00e1tiles<\/a> muestra los datos m\u00e1s actualizados. Si desea seguir el art\u00edculo del blog, puede <a href=\"https:\/\/github.com\/Cheukting\/lstm_anomaly_detection\/tree\/main\/data\" target=\"_blank\" rel=\"noopener\">descargar el conjunto de datos<\/a> que estamos utilizando.<\/p>\n<h4 class=\"wp-block-heading\"><strong>1. Inicie un proyecto de Jupyter<\/strong><\/h4>\n<p>Al iniciar un nuevo proyecto, puede optar por crear uno de Jupyter, que est\u00e1 optimizado para la ciencia de datos. En la ventana <em>New Project<\/em>, puede crear un repositorio Git y determinar qu\u00e9 instalaci\u00f3n de conda utilizar para gestionar su entorno.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539627\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/Jupyter-project-in-PyCharm.png\" alt=\"Iniciar un proyecto de Jupyter en PyCharm\" width=\"1592\" height=\"1282\" \/><\/figure>\n<p>Tras iniciar el proyecto, ver\u00e1 un notebook de ejemplo. Inicie un nuevo notebook de Jupyter para este ejercicio.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539604\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-4.gif\" alt=\"Un notebook de ejemplo en PyCharm\" width=\"716\" height=\"382\" \/><\/figure>\n<p>A continuaci\u00f3n, vamos a configurar <code>requirements.txt<\/code>. Necesitaremos pandas, matplotlib y PyTorch, cuyo nombre es torch en PyPI. Como PyTorch no est\u00e1 incluido en el entorno conda, PyCharm nos dir\u00e1 que nos falta el paquete. Para instalar el paquete, haga clic en la bombilla y seleccione <em>Install all missing packages<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539615\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-5.gif\" alt=\"Instale todos los paquetes que faltan en PyCharm\" width=\"1358\" height=\"762\" \/><\/figure>\n<h4 class=\"wp-block-heading\"><strong>2. Carga e inspecci\u00f3n de los datos<\/strong><\/h4>\n<p>A continuaci\u00f3n, pongamos nuestro conjunto de datos <a href=\"https:\/\/github.com\/Cheukting\/lstm_anomaly_detection\/tree\/main\/data\" target=\"_blank\" rel=\"noopener\">apple_stock_5y.csv<\/a> en la carpeta de datos y cargu\u00e9moslo como un DataFrame de pandas para inspeccionarlo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import pandas as pd\n \ndf = pd.read_csv('data\/apple_stock_5y.csv')\ndf<\/pre>\n<p>Con la tabla interactiva, podemos ver f\u00e1cilmente si falta alg\u00fan dato.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539640\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-6.gif\" alt=\"\" width=\"824\" height=\"442\" \/><\/figure>\n<p>No faltan datos, pero tenemos un problema: nos gustar\u00eda utilizar el precio <em>Close\/Last&lt;<\/em>, pero no es un tipo de dato num\u00e9rico. Hagamos una conversi\u00f3n e inspeccionemos de nuevo nuestros datos:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">df[\"Close\/Last\"] = df[\"Close\/Last\"].apply(lambda x: float(x[1:]))\ndf<\/pre>\n<p>Ahora podemos inspeccionar el precio con la tabla interactiva. Haga clic en el icono de gr\u00e1fico de la izquierda y se crear\u00e1 un gr\u00e1fico. De forma predeterminada, utiliza <em>Date<\/em> como eje x y <em>Volume<\/em> como eje y. Como queremos inspeccionar el precio <em>Close\/Last<\/em>, vaya a la configuraci\u00f3n haciendo clic en el icono del engranaje de la derecha y elija <em>Close\/Last<\/em> como eje y.<\/p>\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539652\" style=\"aspect-ratio: 1.8662790697674418; width: 642px; height: auto;\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-7.gif\" alt=\"\" width=\"642\" height=\"344\" \/><\/figure>\n<h4 class=\"wp-block-heading\"><strong>3. Preparaci\u00f3n de los datos de entrenamiento para el modelo LSTM<\/strong><\/h4>\n<p>A continuaci\u00f3n, tenemos que preparar los datos de entrenamiento que se utilizar\u00e1n en el modelo LSTM. Necesitamos preparar una secuencia de vectores (caracter\u00edstica X), cada uno de los cuales representa una ventana temporal, para predecir el pr\u00f3ximo precio. El siguiente precio formar\u00e1 otra secuencia (objetivo y). Aqu\u00ed podemos elegir el tama\u00f1o de esta ventana de tiempo con la variable <code>lookback<\/code>. El siguiente c\u00f3digo crea secuencias X e Y que m\u00e1s adelante se convertir\u00e1n en tensores de PyTorch:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import torch\n\nlookback = 5\ntimeseries = df[[\"Close\/Last\"]].values.astype('float32')\n\nX, y = [], []\nfor i in range(len(timeseries)-lookback):\n    feature = timeseries[i:i+lookback]\n    target = timeseries[i+1:i+lookback+1]\n    X.append(feature)\n    y.append(target)\n    \nX = torch.tensor(X)\ny = torch.tensor(y)\n\nprint(X.shape, y.shape)<\/pre>\n<p>En t\u00e9rminos generales, cuanto mayor sea la ventana, mayor ser\u00e1 nuestro modelo, ya que el vector de entrada es m\u00e1s grande. Sin embargo, con una ventana mayor, la secuencia de entradas ser\u00e1 m\u00e1s corta, por lo que determinar esta ventana de retrospecci\u00f3n requiere un gran equilibrio. Empezaremos con 5, pero no dude en probar diferentes valores para ver las diferencias.<\/p>\n<h4 class=\"wp-block-heading\"><strong>4. Cree y entrene el modelo<\/strong><\/h4>\n<p>Podemos crear el modelo generando una clase con el <a href=\"https:\/\/pytorch.org\/docs\/stable\/nn.html\" target=\"_blank\" rel=\"noopener\">m\u00f3dulo nn<\/a> en PyTorch antes de entrenarlo. El m\u00f3dulo nn proporciona bloques de construcci\u00f3n, como las diferentes capas de la red neuronal. En este ejercicio, crearemos una sencilla <a href=\"https:\/\/pytorch.org\/docs\/stable\/generated\/torch.nn.LSTM.html\" target=\"_blank\" rel=\"noopener\">capa LSTM<\/a> seguida de una <a href=\"https:\/\/pytorch.org\/docs\/stable\/generated\/torch.nn.Linear.html\" target=\"_blank\" rel=\"noopener\">capa lineal<\/a>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import torch.nn as nn\n\nclass StockModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.lstm = nn.LSTM(input_size=1, hidden_size=50, num_layers=1, batch_first=True)\n        self.linear = nn.Linear(50, 1)\n    def forward(self, x):\n        x, _ = self.lstm(x)\n        x = self.linear(x)\n        return x<\/pre>\n<p>A continuaci\u00f3n, entrenaremos nuestro modelo. Antes de entrenarlo, necesitaremos crear un optimizador, una <a href=\"https:\/\/pytorch.org\/docs\/stable\/nn.html#loss-functions\" target=\"_blank\" rel=\"noopener\">funci\u00f3n de p\u00e9rdida<\/a> utilizada para calcular la p\u00e9rdida entre los valores predichos y los reales de y, y un <a href=\"https:\/\/pytorch.org\/docs\/stable\/data.html#data-loading-order-and-sampler\" target=\"_blank\" rel=\"noopener\">cargador de datos<\/a> para introducir nuestros datos de entrenamiento:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import numpy as np\nimport torch.optim as optim\nimport torch.utils.data as data\n\nmodel = StockModel()\noptimizer = optim.Adam(model.parameters())\nloss_fn = nn.MSELoss()\nloader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=8)<\/pre>\n<p>El cargador de datos puede cambiar el orden de las entradas, puesto que ya hemos creado las ventanas temporales. Esto conserva la relaci\u00f3n secuencial en cada ventana.<\/p>\n<p>El entrenamiento se realiza mediante un bucle <code>for<\/code> que realiza un bucle sobre cada \u00e9poca. Cada 100 \u00e9pocas, imprimiremos la p\u00e9rdida y observaremos mientras el modelo converge:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">n_epochs = 1000\nfor epoch in range(n_epochs):\n    model.train()\n    for X_batch, y_batch in loader:\n        y_pred = model(X_batch)\n        loss = loss_fn(y_pred, y_batch)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n    if epoch % 100 != 0:\n        continue\n    model.eval()\n    with torch.no_grad():\n        y_pred = model(X)\n        rmse = np.sqrt(loss_fn(y_pred, y))\n    print(f\"Epoch {epoch}: RMSE {rmse:.4f}\")<\/pre>\n<p>Comenzamos con 1000 \u00e9pocas, pero el modelo converge con bastante rapidez. No dude en probar otros n\u00fameros de \u00e9pocas de entrenamiento para obtener el mejor resultado.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539664\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/Epochs-for-training.png\" alt=\"\u00c9pocas para el entrenamiento\" width=\"1346\" height=\"1046\" \/><\/figure>\n<p>En PyCharm, una celda que requiera cierto tiempo para ejecutarse mostrar\u00e1 una notificaci\u00f3n sobre cu\u00e1nto tiempo queda y un acceso directo a la celda. Esto es muy \u00fatil cuando se entrenan modelos de aprendizaje autom\u00e1tico, especialmente modelos de aprendizaje profundo, en notebooks de Jupyter.<\/p>\n<h4 class=\"wp-block-heading\"><strong>5. Trazar la predicci\u00f3n y encontrar los errores<\/strong><\/h4>\n<p>A continuaci\u00f3n, crearemos la predicci\u00f3n y generaremos los gr\u00e1ficos junto con la serie temporal real. Tenga en cuenta que tendremos que crear una serie 2D np para que coincida con la serie temporal real. La serie temporal real aparecer\u00e1 en azul, mientras que la serie temporal prevista aparecer\u00e1 en rojo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import matplotlib.pyplot as plt\n\nwith torch.no_grad():\n    pred_series = np.ones_like(timeseries) * np.nan\n    pred_series[lookback:] = model(X)[:, -1, :]\n\nplt.plot(timeseries, c='b')\nplt.plot(pred_series, c='r')\nplt.show()<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539687\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/Plot-the-prediction-and-find-the-errors.png\" alt=\"Genere gr\u00e1ficos de la predicci\u00f3n y encuentre los errores\" width=\"1180\" height=\"856\" \/><\/figure>\n<p>Si observa con atenci\u00f3n, ver\u00e1 que la predicci\u00f3n y los valores reales no se alinean perfectamente. Sin embargo, la mayor\u00eda de las predicciones funcionan bien.<\/p>\n<p>Para inspeccionar los errores de cerca, podemos crear una serie de errores y utilizar la tabla interactiva para observarlos. Esta vez utilizaremos el error absoluto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">error = abs(timeseries-pred_series)\nerror<\/pre>\n<p>Utilice los ajustes para crear un histograma con el valor del error absoluto como eje x y el recuento del valor como eje y.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539434\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/image-3.gif\" alt=\"\" width=\"452\" height=\"362\" \/><\/figure>\n<h4 class=\"wp-block-heading\"><strong>6. Decidir el umbral de anomal\u00eda y visualizar<\/strong><\/h4>\n<p>La mayor\u00eda de los puntos tendr\u00e1n un error absoluto inferior a 6, por lo que podemos fijarlo como umbral de anomal\u00eda. De forma similar a <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#anomaly-threshold\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/01\/anomaly-detection-in-time-series\/#anomaly-threshold\">lo que hicimos para las anomal\u00edas de la colmena<\/a>, podemos trazar los puntos de datos an\u00f3malos en el gr\u00e1fico.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">threshold = 6\nerror_series = pd.Series(error.flatten())\nprice_series = pd.Series(timeseries.flatten())\n\nanomalies_filter = error_series.apply(lambda x: True if x &gt; threshold else False)\nanomalies = price_series[anomalies_filter]\n\nplt.figure(figsize=(14, 8))\nplt.scatter(x=anomalies.index, y=anomalies, color=\"red\", label=\"anomalies\")\nplt.plot(df.index, timeseries, color='blue')\nplt.title('Closing price')\nplt.xlabel('Days')\nplt.ylabel('Price')\nplt.legend()\nplt.show()<\/pre>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-539699\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/Plot-the-anomalous-data-points-in-the-graph.png\" alt=\"Trazar los puntos de datos an\u00f3malos en el gr\u00e1fico\" width=\"1600\" height=\"963\" \/><\/figure>\n<h2 class=\"wp-block-heading\">Resumen<\/h2>\n<p>Los datos de series temporales son una forma com\u00fan de datos utilizados en muchas aplicaciones, incluidas las empresariales y la investigaci\u00f3n cient\u00edfica. Debido a la naturaleza secuencial de los datos de series temporales, se utilizan m\u00e9todos y algoritmos especiales para ayudar a determinar anomal\u00edas en ellos. En este art\u00edculo del blog, demostramos c\u00f3mo identificar anomal\u00edas utilizando la descomposici\u00f3n STL para eliminar estacionalidades y tendencias. Tambi\u00e9n hemos demostrado c\u00f3mo utilizar el aprendizaje profundo y el modelo LSTM para comparar la estimaci\u00f3n prevista y los datos reales con el fin de determinar anomal\u00edas.<\/p>\n<h2 class=\"wp-block-heading\">Detectar anomal\u00edas utilizando PyCharm<\/h2>\n<p>Con el proyecto Jupyter de PyCharm Professional, puede organizar f\u00e1cilmente su proyecto de detecci\u00f3n de anomal\u00edas con muchos archivos de datos y notebooks. Se pueden generar gr\u00e1ficos de salida para inspeccionar las anomal\u00edas y los diagramas son muy accesibles en PyCharm. Otras funciones, como las sugerencias de autocompletado, hacen que navegar por todos los modelos de Scikit-learn y los ajustes de trazado de Matplotlib sea una pasada.<\/p>\n<p>D\u00e9 un nuevo impulso a sus proyectos de ciencia de datos utilizando PyCharm, y <a href=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\/\" target=\"_blank\" rel=\"noopener\">compruebe las funcionalidades de ciencia de datos que ofrece<\/a> para agilizar su flujo de trabajo de ciencia de datos.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\/\" target=\"\" rel=\"noopener\">Comience con PyCharm Pro de forma gratuita<\/a><\/div>\n<\/div>\n\n\n<p>A<em>rt\u00edculo original en ingl\u00e9s de:<\/em><\/p>\n\n\n    <div class=\"about-author \">\n        <div class=\"about-author__box\">\n            <div class=\"row\">\n                <div class=\"about-author__box-img\">\n                    <img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/CheukTingHo-Kimono-e1738750639162-200x200.jpg\" width=\"200\" height=\"200\" alt=\"Cheuk Ting Ho\" loading=\"lazy\"  class=\"avatar avatar-200 wp-user-avatar wp-user-avatar-200 photo avatar-default\">\n                <\/div>\n                <div class=\"about-author__box-text\">\n                                            <h4>Cheuk Ting Ho<\/h4>\n                                                        <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n","protected":false},"author":1086,"featured_media":573221,"comment_status":"closed","ping_status":"closed","template":"","categories":[952,1401],"tags":[8670],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/573205"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/users\/1086"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/comments?post=573205"}],"version-history":[{"count":4,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/573205\/revisions"}],"predecessor-version":[{"id":573234,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/573205\/revisions\/573234"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media\/573221"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media?parent=573205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/categories?post=573205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/tags?post=573205"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/cross-post-tag?post=573205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}