<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>The Data Plumber</title>
<link>https://arminigere1.gitlab.io/myblogs/</link>
<atom:link href="https://arminigere1.gitlab.io/myblogs/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<generator>quarto-1.10.20</generator>
<lastBuildDate>Mon, 09 Nov 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Predictive Maintenance: Catching the Fault</title>
  <dc:creator>Armstrong Nigere</dc:creator>
  <link>https://arminigere1.gitlab.io/myblogs/posts/part3-catch-the-fault/</link>
  <description><![CDATA[ 




<p>In <a href="../../posts/part2-choose-and-check-model/index.html">Part 2</a> we chose and checked a model of the room temperature, using only healthy data. In this part we put it to work: we use it to notice that the heater is failing, from the temperature alone.</p>
<section id="where-we-left-off" class="level2">
<h2 class="anchored" data-anchor-id="where-we-left-off">Where we left off</h2>
<p>All you need from Part 2 is this. We fitted an ARMA(1,1) model to 400 minutes of healthy thermostat data:</p>
<p><span id="eq-model"><img src="https://latex.codecogs.com/png.latex?%0Ay(t)%20-%2021.01%20=%200.911%5C,%5Cbig(y(t-1)%20-%2021.01%5Cbig)%20+%20e(t)%20+%200.130%5C,%20e(t-1)%0A%5Ctag%7B1%7D"></span></p>
<p>It predicts each minute’s temperature from the previous minutes, and the typical size of its error while the room is healthy is <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%5Capprox%200.10"> °C. In <code>statsmodels</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> statsmodels.tsa.arima.model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ARIMA</span>
<span id="cb1-2"></span>
<span id="cb1-3">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ARIMA(y_healthy, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), trend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"c"</span>).fit()</span></code></pre></div></div>
<p>The full script is in <a href="part3_catch_fault.py">part3_catch_fault.py</a>.</p>
</section>
<section id="the-scenario" class="level2">
<h2 class="anchored" data-anchor-id="the-scenario">The scenario</h2>
<p>The data is simulated again, once per minute. For the first 400 minutes the heating is healthy. After that the heater starts to fail: a heating shortfall that grows a little every minute. <strong>The model has never seen this part of the data.</strong></p>
<p>The idea is simple. While the room is healthy, the model’s guesses are close to the real temperature. Once the heater fails, the real temperature falls below the guesses, and the gap between them (the residual) keeps growing. We turn that gap into an alarm.</p>
</section>
<section id="step-1-measure-the-gap" class="level2">
<h2 class="anchored" data-anchor-id="step-1-measure-the-gap">Step 1: measure the gap</h2>
<p>We apply the <strong>same fixed model</strong> to all the data, healthy and faulty, without refitting it. For each minute the model makes a guess, and the <strong>residual</strong> is how far the real temperature is from that guess:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ar(t)%20=%20y(t)%20-%20%5Chat%7By%7D(t)%0A"></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">full <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>(y)                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same parameters, new data</span></span>
<span id="cb2-2">resid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> full.resid</span>
<span id="cb2-3">sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resid[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span>].std()             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># typical size of the error while healthy</span></span>
<span id="cb2-4">z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sigma                      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># residual measured in "sigmas"</span></span></code></pre></div></div>
<p>The number <img src="https://latex.codecogs.com/png.latex?%5Csigma"> is the typical size of the model’s error while everything is healthy, here about 0.10 °C. Dividing by it gives <img src="https://latex.codecogs.com/png.latex?z">, which says <em>how many typical errors</em> the current error is. A value of <img src="https://latex.codecogs.com/png.latex?z%20=%200.5"> is an ordinary wobble, and <img src="https://latex.codecogs.com/png.latex?z%20=%20-4"> is very unusual. This puts every residual on the same scale, whatever the sensor measures.</p>
</section>
<section id="step-2-a-simple-alarm-the-3σ-threshold" class="level2">
<h2 class="anchored" data-anchor-id="step-2-a-simple-alarm-the-3σ-threshold">Step 2: a simple alarm, the ±3σ threshold</h2>
<p>The simplest alarm is a threshold: raise it when the residual goes beyond <img src="https://latex.codecogs.com/png.latex?%5Cpm%203%5Csigma">, meaning <img src="https://latex.codecogs.com/png.latex?%7Cz%7C%20%3E%203">. If the noise is normally distributed, only about 1 point in 370 crosses this line by chance, so a crossing is a good sign that something has changed.</p>
<p>Its weakness is that it reacts to single points. A single spike can give a false alarm, and a slow drift that stays just inside the line can go unnoticed for a long time.</p>
</section>
<section id="step-3-cusum-the-alarm-that-remembers" class="level2">
<h2 class="anchored" data-anchor-id="step-3-cusum-the-alarm-that-remembers">Step 3: CUSUM, the alarm that remembers</h2>
<p>The <strong>CUSUM</strong> (cumulative sum) fixes this by <em>adding up</em> small but persistent errors, like a bucket that slowly fills with water. Since a failing heater makes the temperature fall, we watch for downward shifts:</p>
<p><span id="eq-cusum"><img src="https://latex.codecogs.com/png.latex?%0AS(t)%20=%20%5Cmax%5Cbig(0,%5C;%20S(t-1)%20-%20z(t)%20-%20k%5Cbig)%0A%5Ctag%7B2%7D"></span></p>
<p>In words: take the previous total <img src="https://latex.codecogs.com/png.latex?S(t-1)"> and add the new drop <img src="https://latex.codecogs.com/png.latex?-z(t)">. Subtract a small allowance <img src="https://latex.codecogs.com/png.latex?k"> and never let the total go below zero. The constant <img src="https://latex.codecogs.com/png.latex?k%20=%200.5"> means that wobbles within half a sigma are ignored, so while the room is healthy the bucket drains as fast as it fills and <img src="https://latex.codecogs.com/png.latex?S"> stays near zero. When the residual keeps falling below zero, the bucket fills steadily. When <img src="https://latex.codecogs.com/png.latex?S(t)"> rises above a threshold <img src="https://latex.codecogs.com/png.latex?h%20=%208">, we raise the alarm.</p>
<div id="fig-detect" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Temperature falling after the fault, residual drifting below minus three sigma, and the CUSUM growing">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-detect-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part3-catch-the-fault/fig3_fault_detection.png" class="img-fluid figure-img" alt="Temperature falling after the fault, residual drifting below minus three sigma, and the CUSUM growing">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-detect-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Top: the thermostat signal with the fault starting at minute 400. Middle: the residual measured in sigmas, with the ±3σ threshold. Bottom: the CUSUM, which crosses its threshold at minute 440 (simulated data).
</figcaption>
</figure>
</div>
</section>
<section id="what-the-figure-shows" class="level2">
<h2 class="anchored" data-anchor-id="what-the-figure-shows">What the figure shows</h2>
<ul>
<li><strong>During the healthy period</strong> the residual looks like noise. The ±3σ rule raised one false alarm at minute 22, a single spike. A few false alarms are expected over hundreds of samples.</li>
<li><strong>After the fault</strong> the residual slowly drifts below zero. The first ±3σ alarm came at minute 477, 77 minutes after the fault began.</li>
<li><strong>The CUSUM</strong> alarmed at minute 440, 40 minutes after the fault began. By then the temperature had fallen from about 20.9 °C to 20.0 °C, a drop you would hardly notice in the room. The model noticed it because it knows how the healthy signal normally behaves.</li>
</ul>
</section>
<section id="is-this-a-lucky-run" class="level2">
<h2 class="anchored" data-anchor-id="is-this-a-lucky-run">Is this a lucky run?</h2>
<p>To check, I repeated the experiment with 30 different random seeds. A single ±3σ threshold raised at least one false alarm in 21 of the 30 runs. The CUSUM raised none, and detected the fault after a median of about 40 minutes. With <img src="https://latex.codecogs.com/png.latex?h%20=%205"> instead of 8, about half of the runs had false alarms, so thresholds must be tuned on healthy data. You can repeat this check with the <code>robustness()</code> function in the script.</p>
</section>
<section id="how-control-systems-use-this" class="level2">
<h2 class="anchored" data-anchor-id="how-control-systems-use-this">How control systems use this</h2>
<p>In control and supervision, the residual is the main signal for fault detection. It goes through three steps:</p>
<ol type="1">
<li><strong>Generate:</strong> compare the model’s prediction with the real measurement.</li>
<li><strong>Evaluate:</strong> is the residual the size we would expect from a healthy machine? (<img src="https://latex.codecogs.com/png.latex?%5Cpm%203%5Csigma">, CUSUM)</li>
<li><strong>Decide:</strong> raise an alarm, or not.</li>
</ol>
<p>One catch: a feedback controller can hide a fault by working harder to compensate. A failing heater may keep the room at 21 °C by running at full power. That is why supervision monitors residuals <em>and</em> control effort, not only the output. In our example the thermostat was not compensating, which is why the temperature itself fell.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>The workflow for catching a fault:</p>
<ol type="1">
<li><strong>Build and check a model on healthy data</strong> (Part 2).</li>
<li><strong>Apply the fixed model to new data</strong> and compute the residual.</li>
<li><strong>Measure the residual in sigmas</strong>, using the spread from the healthy period.</li>
<li><strong>Watch it with a threshold and a CUSUM</strong>, and tune both on healthy data.</li>
</ol>
<p>A fault is anything the model cannot explain, and the CUSUM notices small drifts that a single threshold misses.</p>
</section>
<section id="next-time" class="level2">
<h2 class="anchored" data-anchor-id="next-time">Next time</h2>
<p>So far we have looked at one signal at a time. Real machines have many inputs and many outputs, such as heater power, outside temperature and several rooms. In Part 4 we move to <strong>MIMO</strong> (multiple-input, multiple-output) models.</p>


</section>

 ]]></description>
  <category>predictive maintenance</category>
  <category>anomaly detection</category>
  <category>CUSUM</category>
  <category>python</category>
  <guid>https://arminigere1.gitlab.io/myblogs/posts/part3-catch-the-fault/</guid>
  <pubDate>Mon, 09 Nov 2026 00:00:00 GMT</pubDate>
  <media:content url="https://arminigere1.gitlab.io/myblogs/posts/part3-catch-the-fault/cover.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Predictive Maintenance: Choosing and Checking a Model</title>
  <dc:creator>Armstrong Nigere</dc:creator>
  <link>https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/</link>
  <description><![CDATA[ 




<p>In <a href="../../posts/part1-predictive-maintenance/index.html">Part 1</a> we met three models: AR, MA and ARIMA. We also saw the main idea behind anomaly detection: train a model on <strong>healthy</strong> data, then watch the residual. Before we can do that, we need a model we can trust. In this part we answer two questions:</p>
<ol type="1">
<li>How do I choose the model’s parameters <img src="https://latex.codecogs.com/png.latex?p">, <img src="https://latex.codecogs.com/png.latex?d"> and <img src="https://latex.codecogs.com/png.latex?q">?</li>
<li>How do I know the model is good enough?</li>
</ol>
<p>In Part 3 we will use the model to catch a fault.</p>
<section id="the-data" class="level2">
<h2 class="anchored" data-anchor-id="the-data">The data</h2>
<p>I use the thermostat example again, with simulated data so that we know what is really going on. The room temperature is recorded once per minute. For the first 400 minutes the heating is healthy, and <strong>this is the only part we use in this post</strong>. (The heater starts to fail after minute 400, but that is the story of Part 3.)</p>
<p>The healthy data comes from an ARMA(2,1) process around 21 °C. That means a “right answer” exists, but we will pretend we don’t know it, as in real life. The full script is in <a href="part2_choose_check_model.py">part2_choose_check_model.py</a>, and you can run it yourself.</p>
<p>The rule for everything below: <strong>the model is fitted on healthy data only.</strong></p>
</section>
<section id="step-1-choose-d" class="level2">
<h2 class="anchored" data-anchor-id="step-1-choose-d">Step 1: choose d</h2>
<section id="what-stationary-means" class="level3">
<h3 class="anchored" data-anchor-id="what-stationary-means">What “stationary” means</h3>
<p>Picture a dog on a leash walking around its owner. It wanders, but it never goes far, and the size of its wandering stays about the same. That is a <strong>stationary</strong> signal. The thermostat is like this: the room temperature wiggles around 21 °C, and both the average and the size of the wiggles stay the same hour after hour.</p>
<p>Now picture the dog walking away down the road. It has no “home” position, so the average keeps moving. That is a <strong>non-stationary</strong> signal. A room that warms up all morning as the sun comes in is like this.</p>
<p>AR and MA models learn a fixed recipe (the weights <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> and <img src="https://latex.codecogs.com/png.latex?c">) from past data. That recipe only works if the signal behaves the same way all the time. If the average keeps drifting, a recipe learned on the first hour does not fit the next hour.</p>
<div id="fig-stationary" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Three plots: a stationary signal, a trending signal and the differenced trending signal">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-stationary-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/fig0_stationary.png" class="img-fluid figure-img" alt="Three plots: a stationary signal, a trending signal and the differenced trending signal">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-stationary-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Left: a stationary signal stays around a fixed level. Middle: a trending signal drifts away. Right: the trending signal after differencing is stable again (simulated data).
</figcaption>
</figure>
</div>
</section>
<section id="differencing-model-the-change-not-the-level" class="level3">
<h3 class="anchored" data-anchor-id="differencing-model-the-change-not-the-level">Differencing: model the change, not the level</h3>
<p>If a signal trends, we stop modeling the temperature itself and model <strong>how much it changed each minute</strong> instead:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Minute</th>
<th>Temperature</th>
<th>Change from the minute before</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>18.0</td>
<td></td>
</tr>
<tr class="even">
<td>2</td>
<td>18.3</td>
<td>+0.3</td>
</tr>
<tr class="odd">
<td>3</td>
<td>18.7</td>
<td>+0.4</td>
</tr>
<tr class="even">
<td>4</td>
<td>19.0</td>
<td>+0.3</td>
</tr>
<tr class="odd">
<td>5</td>
<td>19.4</td>
<td>+0.4</td>
</tr>
</tbody>
</table>
<p>The temperature climbs steadily, but the changes stay around +0.35, which is stable. We removed the trend, as in the right-hand plot of Figure&nbsp;1. That is <strong>d = 1</strong>: we differenced once. <strong>d = 2</strong> means we differenced the changes again, which is only needed if the change itself keeps trending. Use no more differencing than necessary, because each round adds noise. In practice <img src="https://latex.codecogs.com/png.latex?d"> is almost always 0, 1 or 2.</p>
</section>
<section id="the-adf-test-a-statistical-check" class="level3">
<h3 class="anchored" data-anchor-id="the-adf-test-a-statistical-check">The ADF test: a statistical check</h3>
<p>A plot is not always enough, so we use a test. The <strong>Augmented Dickey-Fuller (ADF) test</strong> begins by assuming the signal is <em>not</em> stationary, meaning it has no tendency to return to an average. It then asks: <em>if that were true, how likely is it that I would see data that keeps coming back to its average like this?</em> That probability is the <strong>p-value</strong>.</p>
<ul>
<li><strong>Small p-value (below 0.05):</strong> such data would be very unlikely under the “not stationary” assumption, so we reject it. The signal is stationary and <strong>d = 0</strong>.</li>
<li><strong>Large p-value (above 0.05):</strong> we cannot rule out non-stationarity. Difference once (<img src="https://latex.codecogs.com/png.latex?d%20=%201">), then test again.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> statsmodels.tsa.stattools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> adfuller</span>
<span id="cb1-2"></span>
<span id="cb1-3">adf_p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> adfuller(y_healthy)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] picks the p-value; p &lt; 0.05 -&gt; stationary</span></span></code></pre></div></div>
<p>For our healthy data the p-value is below 0.05, so the signal is stationary and <strong>d = 0</strong>. A thermostat holds the room around a set value, so this is what we expect.</p>
<p>Be careful not to mix this up with the Ljung-Box test in Step 4, which reads the other way around:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Test</th>
<th>Good result</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>ADF</td>
<td>small p-value</td>
<td>the signal is stationary</td>
</tr>
<tr class="even">
<td>Ljung-Box</td>
<td>large p-value</td>
<td>the residuals are white noise</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="step-2-get-a-first-guess-for-p-and-q" class="level2">
<h2 class="anchored" data-anchor-id="step-2-get-a-first-guess-for-p-and-q">Step 2: get a first guess for p and q</h2>
<p>Two plots give a starting point.</p>
<p><strong>The ACF (autocorrelation function)</strong> asks: how much does the signal resemble its own past? Take the temperature series, shift a copy of it by one minute and measure how similar the two are. That is the correlation at <strong>lag 1</strong>. Do the same with a five-minute shift for lag 5, and so on. Values run from −1 to 1, and 1 means identical behavior. For our thermostat, lag 1 is about 0.93: the temperature now is very similar to a minute ago. The bars shrink as the lag grows, because the room gradually forgets. That slow fading is the memory of the system.</p>
<p><strong>The PACF (partial autocorrelation function)</strong> fixes a flaw of the ACF. The temperature 3 minutes ago seems related to the temperature now, but partly only because both are related to the minutes in between. It is like gossip: A tells B, and B tells C. A and C look connected, but once you know B, A adds nothing. The PACF removes these indirect links. At each lag it asks: <em>once I already know the more recent values, does this older value tell me anything new?</em></p>
<div id="fig-acf" class="quarto-float quarto-figure quarto-figure-center anchored" alt="ACF decaying slowly and PACF with a single spike at lag 1">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-acf-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/fig1_acf_pacf.png" class="img-fluid figure-img" alt="ACF decaying slowly and PACF with a single spike at lag 1">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-acf-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: ACF and PACF of the healthy thermostat data. The shaded band is the 95% confidence range: bars inside it are not significantly different from zero (simulated data).
</figcaption>
</figure>
</div>
<p>The shaded band marks the range where a bar could be just random chance, so bars inside it count as zero. Use these rules to read the plots:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>What you see</th>
<th>It suggests</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>PACF cuts off after lag <img src="https://latex.codecogs.com/png.latex?p">, ACF decays slowly</td>
<td>AR(<img src="https://latex.codecogs.com/png.latex?p">)</td>
</tr>
<tr class="even">
<td>ACF cuts off after lag <img src="https://latex.codecogs.com/png.latex?q">, PACF decays slowly</td>
<td>MA(<img src="https://latex.codecogs.com/png.latex?q">)</td>
</tr>
<tr class="odd">
<td>Both decay</td>
<td>a mix, so let AIC and BIC decide</td>
</tr>
</tbody>
</table>
<p>In Figure&nbsp;2 the ACF decays slowly, and the PACF has one clear spike at lag 1, then falls inside the band. Once you know last minute’s temperature, older minutes add nothing new, so this points to <img src="https://latex.codecogs.com/png.latex?p%20=%201">. It is only a guess, and real data is rarely this clean, so in the next step we let the numbers compare candidates.</p>
</section>
<section id="step-3-compare-models-with-aic-and-bic" class="level2">
<h2 class="anchored" data-anchor-id="step-3-compare-models-with-aic-and-bic">Step 3: compare models with AIC and BIC</h2>
<p>A bigger model always fits the training data better, but it may just be memorizing noise. AIC and BIC reward a good fit and penalize complexity:</p>
<p><span id="eq-aic"><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BAIC%7D%20=%202k%20-%202%5Cln%20%5Chat%7BL%7D%0A%5Ctag%7B1%7D"></span></p>
<p><span id="eq-bic"><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BBIC%7D%20=%20k%5Cln%20n%20-%202%5Cln%20%5Chat%7BL%7D%0A%5Ctag%7B2%7D"></span></p>
<p>Here <img src="https://latex.codecogs.com/png.latex?k"> is the number of parameters (the AR weights, the MA weights, the mean and the noise variance), <img src="https://latex.codecogs.com/png.latex?n"> the number of samples, and <img src="https://latex.codecogs.com/png.latex?%5Chat%7BL%7D"> the best likelihood the model reaches. <strong>Lower is better.</strong> BIC punishes extra parameters more, so it tends to pick simpler models.</p>
<p>We fit every combination of <img src="https://latex.codecogs.com/png.latex?p"> and <img src="https://latex.codecogs.com/png.latex?q"> from 0 to 3:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> statsmodels.tsa.arima.model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ARIMA</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>):</span>
<span id="cb2-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> q <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>):</span>
<span id="cb2-5">        res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ARIMA(y_healthy, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(p, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, q), trend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"c"</span>).fit()</span>
<span id="cb2-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(p, q, res.aic, res.bic)</span></code></pre></div></div>
<p>Here are some of the results, together with the Ljung-Box check from the next step:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th><img src="https://latex.codecogs.com/png.latex?k"></th>
<th>AIC</th>
<th>BIC</th>
<th>Ljung-Box p</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>MA(2)</td>
<td>4</td>
<td>−451.0</td>
<td>−435.0</td>
<td>&lt; 0.001</td>
</tr>
<tr class="even">
<td>AR(1)</td>
<td>3</td>
<td>−696.8</td>
<td><strong>−684.9</strong></td>
<td>0.363</td>
</tr>
<tr class="odd">
<td>AR(2)</td>
<td>4</td>
<td>−699.5</td>
<td>−683.5</td>
<td>0.701</td>
</tr>
<tr class="even">
<td><strong>ARMA(1,1)</strong></td>
<td>4</td>
<td><strong>−700.1</strong></td>
<td>−684.1</td>
<td>0.754</td>
</tr>
<tr class="odd">
<td>ARMA(2,1)</td>
<td>5</td>
<td>−699.0</td>
<td>−679.1</td>
<td>0.781</td>
</tr>
<tr class="even">
<td>ARMA(3,3)</td>
<td>8</td>
<td>−697.3</td>
<td>−665.4</td>
<td>0.422</td>
</tr>
</tbody>
</table>
<p><em>Your numbers may differ in the last digits.</em></p>
<p>Three things stand out:</p>
<ul>
<li><strong>MA(2) is far behind.</strong> It has no memory of past values, which this signal clearly needs.</li>
<li><strong>The next four are almost tied.</strong> A difference of less than about 2 points is usually not meaningful.</li>
<li><strong>The big ARMA(3,3) does not win.</strong> More parameters did not buy a better model, and the penalty shows.</li>
</ul>
<p>AIC picks ARMA(1,1) and BIC picks the simpler AR(1). When they disagree, AIC favors prediction accuracy and BIC favors simplicity. Our goal is good predictions, because the residual is what we monitor, so I go with <strong>ARMA(1,1)</strong>. AR(1) would also be a reasonable choice.</p>
</section>
<section id="step-4-is-the-model-good-enough" class="level2">
<h2 class="anchored" data-anchor-id="step-4-is-the-model-good-enough">Step 4: is the model good enough?</h2>
<p>AIC and BIC pick the best among the candidates, but they cannot tell you whether the winner is actually <em>good</em>. For that, look at the residuals. If the model captured every real pattern, what is left should be <strong>white noise</strong>: random, with no structure left to learn.</p>
<p>The <strong>Ljung-Box test</strong> checks this with one number. Its starting assumption is that the residuals are white. It adds up the squared autocorrelations <img src="https://latex.codecogs.com/png.latex?%5Chat%5Crho_k"> of the residuals up to lag <img src="https://latex.codecogs.com/png.latex?h">:</p>
<p><span id="eq-lb"><img src="https://latex.codecogs.com/png.latex?%0AQ%20=%20n(n+2)%5Csum_%7Bk=1%7D%5E%7Bh%7D%5Cfrac%7B%5Chat%5Crho_k%5E%7B%5C,2%7D%7D%7Bn-k%7D%0A%5Ctag%7B3%7D"></span></p>
<p>and compares <img src="https://latex.codecogs.com/png.latex?Q"> with a <img src="https://latex.codecogs.com/png.latex?%5Cchi%5E2"> distribution with <img src="https://latex.codecogs.com/png.latex?h-(p+q)"> degrees of freedom.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> statsmodels.stats.diagnostic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> acorr_ljungbox</span>
<span id="cb3-2"></span>
<span id="cb3-3">acorr_ljungbox(res.resid[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>:], lags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>], model_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> q)</span></code></pre></div></div>
<ul>
<li><strong>p-value above 0.05:</strong> no leftover pattern. The model is good enough.</li>
<li><strong>p-value below 0.05:</strong> structure remains, so the model is too simple (underfitted). Try a higher <img src="https://latex.codecogs.com/png.latex?p"> or <img src="https://latex.codecogs.com/png.latex?q">, recheck <img src="https://latex.codecogs.com/png.latex?d">, or add a seasonal component if the pattern repeats daily.</li>
</ul>
<div id="fig-white" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Residual autocorrelation of MA(2) with large bars and of ARMA(1,1) with bars inside the band">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-white-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/fig2_residual_whiteness.png" class="img-fluid figure-img" alt="Residual autocorrelation of MA(2) with large bars and of ARMA(1,1) with bars inside the band">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-white-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Autocorrelation of the residuals. Left: a model that is too simple leaves strong structure behind. Right: the chosen ARMA(1,1) leaves only noise (simulated data).
</figcaption>
</figure>
</div>
<p>Figure&nbsp;3 shows the difference. The workflow is: <strong>AIC and BIC choose the winner, Ljung-Box confirms it.</strong></p>
<p>Our final model, fitted on the healthy data, is:</p>
<p><span id="eq-final"><img src="https://latex.codecogs.com/png.latex?%0Ay(t)%20-%2021.01%20=%200.911%5C,%5Cbig(y(t-1)%20-%2021.01%5Cbig)%20+%20e(t)%20+%200.130%5C,%20e(t-1)%0A%5Ctag%7B4%7D"></span></p>
<p>The number 21.01 is the mean temperature. The weight 0.911 says the room has a long memory: this minute’s temperature is mostly last minute’s, which is the thermal inertia we talked about in Part 1. The weight 0.130 on the previous error is small. In the <code>statsmodels</code> output these appear as <code>const</code>, <code>ar.L1</code> and <code>ma.L1</code>, which correspond to the mean, <img src="https://latex.codecogs.com/png.latex?%5Ctheta_1"> and <img src="https://latex.codecogs.com/png.latex?c_1"> in the notation of Part 1. The typical size of the residual is <img src="https://latex.codecogs.com/png.latex?%5Csigma%20%5Capprox%200.10"> °C. We will use this model in Part 3.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>The workflow for choosing and checking a model of a single signal:</p>
<ol type="1">
<li><strong>Check stationarity</strong> (ADF) and pick <img src="https://latex.codecogs.com/png.latex?d">.</li>
<li><strong>Look at ACF and PACF</strong> for a first guess of <img src="https://latex.codecogs.com/png.latex?p"> and <img src="https://latex.codecogs.com/png.latex?q">.</li>
<li><strong>Fit candidates on healthy data</strong> and compare AIC and BIC.</li>
<li><strong>Confirm with Ljung-Box</strong> that the residuals are white.</li>
</ol>
<p>Every step uses healthy data to learn what normal looks like. Once the model passes these checks, we can trust its predictions, and anything it cannot explain is worth a closer look.</p>
</section>
<section id="next-time" class="level2">
<h2 class="anchored" data-anchor-id="next-time">Next time</h2>
<p>In <a href="../../posts/part3-catch-the-fault/index.html">Part 3</a>, we apply this model to new data from the thermostat, where the heater slowly starts to fail, and use the residual to raise an alarm.</p>


</section>

 ]]></description>
  <category>predictive maintenance</category>
  <category>ARIMA</category>
  <category>system identification</category>
  <category>python</category>
  <guid>https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/</guid>
  <pubDate>Sun, 08 Nov 2026 00:00:00 GMT</pubDate>
  <media:content url="https://arminigere1.gitlab.io/myblogs/posts/part2-choose-and-check-model/cover.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Predictive Maintenance: Why I Fell Down the Rabbit Hole</title>
  <dc:creator>Armstrong Nigere</dc:creator>
  <link>https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/</link>
  <description><![CDATA[ 




<p>I have wanted to write this series for a long time, and here is the first piece.</p>
<section id="why-this-series" class="level2">
<h2 class="anchored" data-anchor-id="why-this-series">Why this series</h2>
<p>At university, I meet system identification and estimation as theory: equations, proofs and toolbox commands. It is easy to pass an exam and still not feel how any of it works on a real machine. This series is my attempt to close that gap. I document what I learn as I go, and for each concept I follow one real-life example, such as a thermostat, from the idea to the code to a working fault detector. I write for people who are new to the topic, because not long ago that was me.</p>
</section>
<section id="a-bit-about-me" class="level2">
<h2 class="anchored" data-anchor-id="a-bit-about-me">A bit about me</h2>
<p>I am an engineering student specializing in data science and data engineering, with a focus on <strong>System Identification &amp; Data Analysis</strong> and <strong>Adaptive Learning, Estimation &amp; Supervision</strong>.</p>
</section>
<section id="how-i-got-here" class="level2">
<h2 class="anchored" data-anchor-id="how-i-got-here">How I got here</h2>
<p>It all started with trains. I wondered how complicated it must be to perform maintenance on trains. While doing some research, I read an article explaining how sensors are placed on trains to collect data. This data is then analyzed and used for forecasting, to predict when maintenance will be needed. By doing this, operators were cutting costs significantly.</p>
<p>That was my introduction to <strong>predictive maintenance</strong> and <strong>anomaly detection</strong>, and I found it interesting enough to pursue a postgraduate degree in the field.</p>
</section>
<section id="what-this-series-covers" class="level2">
<h2 class="anchored" data-anchor-id="what-this-series-covers">What this series covers</h2>
<p>System identification means building a mathematical model of a system from its input and output data. There are three approaches:</p>
<ul>
<li><strong>White box:</strong> you know the physics and write the equations directly.</li>
<li><strong>Grey box:</strong> you know part of the physics, and data fills in the rest.</li>
<li><strong>Black box:</strong> you do not model the internals at all. You let the data describe the behavior.</li>
</ul>
<p>This series focuses on <strong>black box, data-driven models</strong>. We will start with the simplest kind: <strong>SISO (single-input, single-output)</strong> models, which look at one signal at a time, such as the temperature reading from your house thermostat.</p>
</section>
<section id="ar-ma-and-arima" class="level2">
<h2 class="anchored" data-anchor-id="ar-ma-and-arima">AR, MA and ARIMA</h2>
<p>In this part, we will look at three models. They all predict the next value of a signal from its own history.</p>
<p><strong>AR (AutoRegressive).</strong> The next value depends on the previous values of the signal itself:</p>
<p><span id="eq-ar"><img src="https://latex.codecogs.com/png.latex?%0Ay(t)%20=%20%5Ctheta_1%5C,%20y(t-1)%20+%20%5Ctheta_2%5C,%20y(t-2)%20+%20%5Cdots%20+%20%5Ctheta_p%5C,%20y(t-p)%20+%20e(t)%0A%5Ctag%7B1%7D"></span></p>
<p>where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?y(t)">: the value of the signal <strong>now</strong>, for example the thermostat temperature at the current time step <img src="https://latex.codecogs.com/png.latex?t">.</li>
<li><img src="https://latex.codecogs.com/png.latex?y(t-1)">: the value <strong>one time step ago</strong>, meaning the previous measurement. In the same way, <img src="https://latex.codecogs.com/png.latex?y(t-2)"> is two steps ago, and so on.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Ctheta_1,%20%5Cdots,%20%5Ctheta_p">: weights that the model learns from data. Each one says how much the matching past value matters for predicting the next one.</li>
<li><img src="https://latex.codecogs.com/png.latex?p">: how many past values the model looks back at.</li>
<li><img src="https://latex.codecogs.com/png.latex?e(t)">: the error (or noise) term. It is the part of the current value that the past values cannot explain: random, unpredictable, and zero on average. In engineering it is called <strong>white noise</strong>.</li>
</ul>
<p>Think of your thermostat reading. The room temperature changes slowly, so where it is now tells you where it will be a few minutes from now.</p>
<p><strong>MA (Moving Average).</strong> The next value depends on past prediction errors (the “shocks” the model did not see coming):</p>
<p><span id="eq-ma"><img src="https://latex.codecogs.com/png.latex?%0Ay(t)%20=%20e(t)%20+%20c_1%5C,%20e(t-1)%20+%20c_2%5C,%20e(t-2)%20+%20%5Cdots%20+%20c_q%5C,%20e(t-q)%0A%5Ctag%7B2%7D"></span></p>
<p>where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?e(t)">: the error at the current time step, the new random shock that has just hit the system.</li>
<li><img src="https://latex.codecogs.com/png.latex?e(t-1)">: the error <strong>one step ago</strong>, the shock from the previous time step. <img src="https://latex.codecogs.com/png.latex?e(t-2)"> is the one before that, and so on.</li>
<li><img src="https://latex.codecogs.com/png.latex?c_1,%20%5Cdots,%20c_q">: weights the model learns. Each says how much of an earlier shock is still affecting the signal.</li>
<li><img src="https://latex.codecogs.com/png.latex?q">: how many past errors the model remembers.</li>
</ul>
<p>(I use <img src="https://latex.codecogs.com/png.latex?c"> for these weights so they are not confused with the <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> weights of the AR model.)</p>
<p>Imagine someone opens a window. The temperature dips, then recovers over the next few minutes. That short-lived disturbance is what MA captures.</p>
<div id="fig-window" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Room temperature dropping when a window opens, then recovering">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-window-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/graph1_ar_ma_window.png" class="img-fluid figure-img" alt="Room temperature dropping when a window opens, then recovering">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-window-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: A window is opened at <img src="https://latex.codecogs.com/png.latex?t=150">. The temperature drops, then recovers gradually. The dashed line is the model’s one-step prediction (simulated data).
</figcaption>
</figure>
</div>
<p><strong>ARIMA.</strong> AR and MA combined, with an added step called <strong>integration</strong> (I). Many real signals trend upward or downward over time, like the room warming up all morning as the sun comes in. ARIMA handles this by modeling the <em>change</em> between samples (differencing) instead of the raw values:</p>
<p><span id="eq-diff"><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%20y(t)%20=%20y(t)%20-%20y(t-1)%0A%5Ctag%7B3%7D"></span></p>
<p>The model is written <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BARIMA%7D(p,%20d,%20q)">, where:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?p">: how many past values to use (AR order)</li>
<li><img src="https://latex.codecogs.com/png.latex?d">: how many times to difference the data</li>
<li><img src="https://latex.codecogs.com/png.latex?q">: how many past errors to use (MA order)</li>
</ul>
<div id="fig-diff" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Trending temperature signal and its differenced, stable version">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-diff-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/graph2_differencing.png" class="img-fluid figure-img" alt="Trending temperature signal and its differenced, stable version">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-diff-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Top: a room that warms steadily shows a clear trend. Bottom: after differencing with <img src="https://latex.codecogs.com/png.latex?d=1"> the trend is removed and the signal is stable (simulated data).
</figcaption>
</figure>
</div>
</section>
<section id="why-this-matters-for-anomaly-detection" class="level2">
<h2 class="anchored" data-anchor-id="why-this-matters-for-anomaly-detection">Why this matters for anomaly detection</h2>
<p>Here is the idea that ties it all together. Train a model on <strong>healthy</strong> data. Then, as new data comes in, compare the model’s prediction <img src="https://latex.codecogs.com/png.latex?%5Chat%7By%7D(t)"> with what the sensor actually reports, <img src="https://latex.codecogs.com/png.latex?y(t)">. The difference is the <em>residual</em>:</p>
<p><span id="eq-res"><img src="https://latex.codecogs.com/png.latex?%0Ar(t)%20=%20y(t)%20-%20%5Chat%7By%7D(t)%0A%5Ctag%7B4%7D"></span></p>
<p>While the machine is healthy, the residual stays small. When something starts to go wrong, the residual grows, and that is your early warning.</p>
<p>For the thermostat: if the heating is working normally, the model predicts the temperature well. If the heater starts failing, the real temperature keeps falling below what the model expects, and the residual grows.</p>
<div id="fig-anomaly" class="quarto-float quarto-figure quarto-figure-center anchored" alt="Measured versus predicted temperature and the growing residual after a heater fault">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-anomaly-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/graph3_anomaly_residual.png" class="img-fluid figure-img" alt="Measured versus predicted temperature and the growing residual after a heater fault">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-anomaly-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: Top: measured versus predicted temperature. Bottom: the residual stays near zero while the heater is healthy, then crosses the <img src="https://latex.codecogs.com/png.latex?%5Cpm%203%5Csigma"> threshold after the fault begins (simulated data).
</figcaption>
</figure>
</div>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>To sum up: <strong>AR</strong> models the <em>memory</em> of a signal, how its past values shape the next one. <strong>MA</strong> models <em>disturbances</em>, the short-lived shocks that a healthy system experiences, like an open window. <strong>ARIMA</strong> combines both and also removes trends through differencing.</p>
<p>An important point is that the disturbances MA captures are <em>normal</em>, not faults. A fault only shows up when the residual becomes much larger than these normal shocks. All of this relies on past data, and that data must come from a <strong>healthy</strong> system, so the model learns what normal looks like.</p>
</section>
<section id="next-time" class="level2">
<h2 class="anchored" data-anchor-id="next-time">Next time</h2>
<p>In <a href="../../posts/part2-choose-and-check-model/index.html">Part 2</a>, we will look at how to choose the model’s parameters and check that it is good enough. In Part 3, we will use it to catch a fault in Python.</p>


</section>

 ]]></description>
  <category>predictive maintenance</category>
  <category>anomaly detection</category>
  <category>ARIMA</category>
  <category>system identification</category>
  <guid>https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/</guid>
  <pubDate>Sat, 07 Nov 2026 00:00:00 GMT</pubDate>
  <media:content url="https://arminigere1.gitlab.io/myblogs/posts/part1-predictive-maintenance/cover.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
