LaminAir¶
Here I introduce the LaminAir. Safer indoor dining. Safer working in the office. Double-digit exposure reduction factors, while breathing 12 inches away from the device. At least 14 times better performance than SmartAir's QT3 at that distance in reducing inhalation of bioaerosols like SARS-CoV-2 and other respiratory pathogens.






Much thanks to @CleanAirStars and @ee_kelsey for doing the initial experiments that became the inspiration for LaminAir.
Available for purchase at https://breathesafe-llc.myshopify.com/products/laminair.
How well does it work?¶
I conducted a bunch of OSHA exercises to assess exposure reduction. I had the LaminAir 12 inches away from the face.
Overall, going through the set of OSHA exercises, I found that LaminAir has at least double-digit exposure reduction factors, which makes it a viable addition to one's multilayers of protection. Here are the results of my experiments:

Here's a way to compare different mitigations in terms of their exposure reduction factor:
A lot of it is over the N95 lower bound of 20. An exposure reduction of 20 for an N95 typically means that the air is 20 times cleaner in terms of number of particles. i.e. If there are 100 particles to begin with, 95% get filtered out and only 5 pass through. $100 / 5 = 20$.
Note: Instead of using the number of particles, however, I measured using mass concentration. If the distribution of particles consists of mostly ultrafine particles (tiny, low mass particles) than fine particles, then a good mass concentration reduction can be achieved by mostly removing the bigger particles even while not making a dent on the smaller ultrafine particles. However, since I'm using a HEPA filter, which should filter with high efficiency across the board, I don't think the distinction matters that much.
How does it work?¶
To appreciate how it works, let's first dive into a discussion of lowering inhaled dose, and the exposure reduction factor.
Lowering the dose¶
Dose is essentially concentration times time. Lowering the inhaled dose leads to a lower probability of infection.
$$ \begin{equation} \begin{aligned} \text{Dose} = \text{Concentration} \times \text{time} \end{aligned} \end{equation} $$
To get a lower dose, one could:
- remove the infector. This would mean concentration of dirty air will be 0.
- if removing the infector is not possible (i.e. concentration is greater than 0), one could
- spend less time in a room with the infector
- clean the air to varying degrees before it is inhaled by an individual, via masking and air cleaning tools
When there is a constant generation rate of infectious aerosols, and there is a constant generation rate of clean air, it can be shown that in the long run, the concentration of a contaminant is the ratio between the rate of dirty air generation divided by the rate of clean air generation. See Appendix for the derivation, if interested.
$$ \begin{equation} \begin{aligned} \lim_{t \rightarrow \infty}\text{Concentration(t)} &= \frac{\text{generation rate of dirty air}}{\text{clean air delivery rate}} \end{aligned} \end{equation} $$
Concentration can be measured in a number of ways. One way is mass concentration, measured in micrograms per cubic meter (µg / m$^3$). Another is number of particles (e.g. parts per million -- ppm).
Exposure Reduction Factors¶
Another concept that will be useful in understanding how air cleaners and masks affect risk is the exposure reduction factor (also known as fit factor). In general:
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor(device)} &= \frac{\text{Concentration in the breathing area with the device turned off}}{\text{Concentration in the breathing area with the device turned on}} \end{aligned} \end{equation} $$
For masks, when the air is well-mixed (i.e. fans are turned on mixing the air), then in theory, sampling location doesn't affect concentration. One can sample anywhere and the concentration wouldn't change. For a respirator or a mask, we can simultaneously sample with two sensors, one in the breathing area (within the mask), and one near the breathing area (outside the mask):
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor(mask)} &= \frac{\text{Concentration not in the breathing area (outside the mask)}}{\text{Concentration in the breathing area (within the mask)}} \end{aligned} \end{equation} $$
The higher the exposure reduction factor, the more protective the device is for an individual. Different exercises can have different exposure reduction factors for the same individual. Exposure reduction factors help us understand how protective masks and respirators and room air cleaners, and personal air cleaners. Some will be a little bit more protective than others, while some much more so than others.
Case Study¶
In the NIOSH study of the Effectiveness of DIY Air Cleaners, researchers investigated how much DIY Air Cleaners such as Corsi-Rosenthal boxes decreased the concentration of contaminants in the air.

They had a classroom with 1.89 ACH of ventilation, with 6357 cubic feet as the volume of the room. Let's find out what the exposure reduction factor is when adding 2 CR boxes to a classroom with minimal ventilation.
$$ \begin{equation} \begin{aligned} \text{ACH} &= \frac{\text{CADR} (\frac{ \text{ ft}^3}{\text{hr}}) }{ \text{Volume} (\text{ft}^3)} \end{aligned} \end{equation} $$
Steps:
- Figure out the clean air delivery rate in the minimal ventilation case, so we can compute concentration for that.
- Figure out the clean air delivery rate after adding 2 CR boxes, so we can compute concentration for that.
- Divide the two to get the exposure reduction factor.
We'll also take a look at the experimental results to see what exposure reduction factor the researchers got.
Solving for Clean Air Delivery Rate¶
Solving for the clean air delivery rate, which initially was only made of ventilation, we find that they had 200 cubic feet per minute of ventilation:
$$ \begin{equation} \begin{aligned} 1.89 (\frac{1}{\text{hr}}) &= \frac{\text{CADR } (\frac{\text{ft}^3}{\text{hr}})}{6357 \text{ ft}^3} \\ 12014.73 (\frac{\text{ft}^3}{\text{hr}}) &= \text{CADR } (\frac{\text{ft}^3}{\text{hr}}) \\ 12014.73 (\frac{\text{ft}^3}{\text{hr}}) \times \frac{1 \text{ hr}}{60 \text{ min}} &= \text{CADR } (\frac{\text{ft}^3}{hr}) \times \frac{1 \text{ hr}}{60 \text{ min}} \\ 200 (\frac{\text{ft}^3}{\text{min}})&= \text{CADR } (\frac{\text{ft}^3}{\text{min}}) \\ \end{aligned} \end{equation} $$
Concentration With Minimal Ventilation¶
Let's say we don't know the generation rate of aerosols $g$. Then the concentration in the long run will be:
$$ \begin{equation} \begin{aligned} \lim_{t \rightarrow \infty}\text{Concentration(t)} &= \frac{g \text{ CFM}}{200 \text{ CFM}} \\ &= \frac{g}{200} \\ \end{aligned} \end{equation} $$
Concentration With Minimal Ventilation + 2 CR boxes¶
In the study, the best performing CR box the NIOSH researchers created had an estimated clean air delivery rate of 655.5 CFM. See Appendix A. supplementary data for details. What if we added 2 CR boxes, each outputting 655.5 CFM? What would the concentration be?
$$ \begin{equation} \begin{aligned} \lim_{t \rightarrow \infty}\text{Concentration(t)} &= \frac{g \text{ CFM}}{200 \text{ CFM} + (655.5 \times 2) { CFM}} \\ &= \frac{g}{1511} \\ \end{aligned} \end{equation} $$
Exposure Reduction Factor for adding 2 CR boxes to minimum ventilation¶
How much cleaner is the air after adding the 2 CR boxes, relative to without the CR boxes?
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor} &= \frac{\text{Concentration of air with 200 CFM ventilation only}}{\text{Concentration of air with 200 CFM ventilation and 1311 CFM filtration}} \\ &= \frac{g / 200 }{g / 1511} \\ &= 7.415 \end{aligned} \end{equation} $$
In theory, assuming the room is well-mixed, people in the room will be decreasing their exposure by a factor of 7.4 by adding the 2 CR boxes, while keeping everything else constant.
What does the exposure reduction factor of 7.4 mean? It means that adding the 2 CR boxes to the room with minimal ventilation will result in 7.4 times cleaner air. For example, let's say the room with minimal ventilation had 100 micrograms per cubic meter (µg / m3). Then
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor} &= \frac{\text{Concentration with just 200 CFM ventilation}}{\text{Concentration with 2 CR boxes on max speed + 200 CFM ventilation}} \\ &= 7.4 \\ &= \frac{100 \frac{ \text{ µg}}{ \text{ m3}}}{13.5 \frac{ \text{ µg}}{ \text{ m3}}} \end{aligned} \end{equation} $$
We expect the concentration in the 2 CR boxes + 200 CFM regime to be around 13 micrograms per cubic meter, a reduction by a factor of 7.4.
Exposure Reduction Factor from Experimental Results¶
How does this compare to the experimental results of the NIOSH study on the Effectiveness of DIY Air Cleaners?
In NIOSH's experimental study, the addition of the two best-performing CR boxes that outputed about 1311 CFM resulted in a reduction of 78%, or an exposure reduction factor of 4.5, which is slightly lower than the 7.4 estimated earlier here. Assuming 100 µg / m3 to begin with, a reduction of 78% means that 22 µg / m3 would be left over, or a exposure reduction factor of 4.5:
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor} &= \frac{\text{Concentration with just 200 CFM ventilation}}{\text{Concentration with 2 CR boxes on max speed + 200 CFM ventilation}} \\ &= \frac{100 \frac{ \text{ µg}}{ \text{ m3}}}{22 \frac{ \text{ µg}}{ \text{ m3}}} \\ &= 4.5 \end{aligned} \end{equation} $$
I think the discrepancy could be caused by a multitude of factors -- only sampling from three positions and the room not actually being well-mixed. A fully well-mixed room is an ideal and doesn't happen in practice. However, the takeaway is the following. In terms of order of magnitude, the exposure reduction factor as predicted by theory and as computed in an actual experiment are in the single digits.
What if the susceptible wears a fit-tested N95?¶
An N95 respirator that is fit-tested and well-engineered (so that it retains the structural integrity even after jaw movements) should at least remove 95% of (non-oil) particles. If 95% are removed, then 5% remain. This results in a lower bound exposure reduction factor of 20:
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor(N95)} &= \frac{1}{1 - 0.95} \\ &= 20 \end{aligned} \end{equation} $$
How does it work, generally speaking?
The high filtration efficiency of an N95 plus no leakage means that the concentration being breathed in by the user is at least 20 times cleaner. So key ingredients are:
- High filtration efficiency filter
- some way to ensure that the clean air being produced by the N95 isn't mixing that much with the dirty air right before someone breathes in the clean air.
How can we develop an air cleaning device that produces a clean air bubble?¶

For the high filtration efficiency component, we can emulate what an N95 does by using a HEPA filter, which filters 99.97% of particulates.
If we were to breathe 0 inches away from the device, we should in theory get the following exposure reduction factor:
$$ \begin{equation} \begin{aligned} \text{Exposure Reduction Factor(HEPA at 0 inches away)} &= \frac{ 1 }{ 1 - 99.97\%} \\ &= 3,333 \end{aligned} \end{equation} $$
However, it is impractical to breathe 0 inches away from the device. In the next sections we cover a technique to preserve as much of the clean air as possible without a solid physical barrier, which is the laminar flow technique. It decreases mixing of the highly concentrated clean air with surrounding air (i.e. reduces entrainment).
What is Laminar Flow?¶
Air with laminar flow means that the particles of air are moving straight. This is in contrast to turbulent flow, where particles of air aren't flowing in the same direction.
The image above shows a fluid moving through a pipe in a laminar way vs. a turbulent way. Why does this distinction matter for air cleaning and risk reduction? Laminar flow leads to less entrainment than turbulent flow. Entrainment is the process of surrounding air mixing with some other air. If we have a clean air source and we are trying to get high exposure reduction factors (i.e. very low concentration of dirty air), we want it to keep it as clean as possible by decreasing entrainment from surrounding air which may contain infectious aerosols.
Traditional CR Box design produces turbulent flow¶
Turbulent flow leads to more mixing of clean air with the dirty air. If we were to inhale directly from a device without some physical barrier (e.g. a hood), then turbulent flow will increase the concentration of dirty air in what is being breathed.
The traditional "pull configuration" of the CR box is known to produce turbulent air. The box fan pulls air from the filters and then pushes it out in a swirly way, because of the rotation of the fan and because there isn't some material straightening the air as it goes out. The swirl motion pulls in a bunch of dirty air from the sides.

A more optimal configuration for risk reduction for an individual would be to switch to a push configuration.
Push configuration produces more laminar flow¶
Having the fan pull air first, and then pushing it through a filter, produces more laminar flow. Why so? The filter is a bunch of randomly scattered fibers. When the swirly air from a fan hits the filter, the air gets straightened. While some entrainment still exists, it's not as bad as that from a pull configuration.

TODO: use yellow color for what's coming out of the device, to denote that people are breathing in dirtier air because the filter behind a CR box is MERV-13, not HEPA.
Slower speeds lead to less entrainment¶
Air moves from high pressure to low pressure. High pressure areas have more particles (more dense) bumping against one another, pushing other particles towards low pressure areas, which have less particles.

There is lower pressure in air moving at high speed versus air moving at lower speed. Therefore if one is using the high speed setting for an personal air cleaning device, the breathing area will have lower air pressure than in the case of using a lower speed setting. The pressure differential between the surroundings and the breathing area causes entrainment. There will be a higher pressure differential with the high speed setting compared to the low speed setting, and thus, more entrainment in the former than in the latter.


Filters with a wide area are better than filters with a smaller area¶
When trying to breathe in highly concentrated clean air from a device, a wider area filter is most likely to give better results than a smaller area filter, given the same distance away from the device. When the air is generally still, entrainment is happening in the perimeter of the filter. If one is breathing in from the center of the device, using a wider filter versus a narrower filter, makes it so that the sides are farther from the center, so the less entrained air will be in the middle for the wider filter vs. the narrower filter.
Recap¶
The following design principles are behind the LaminAir, which make it much more effective for personal protection than other personal air cleaners:
- High filtration efficiency (HEPA) filter.
- Push configuration (as opposed to pull configuration), leading to more Laminar flow, and less entrainment.
- Relatively wide filter compared to the existing competitors, leading to less entrainment in the middle, given a reasonable distance away from the device.
- Slow airflow coming out of the filter, leading to less entrainment.
Limitations ⚠️¶
One can get very clean air when breathing 12 inches away from the device. However, there are caveats. Since there is no solid physical barrier enclosing the clean air before it is breathed in, laminar flow is sensitive to other air currents. Generally speaking, a good rule of thumb is if you can feel some sort of wind from other sources, you might not be getting the concentrated clean air.
- Stay away from other devices that mix the air, especially box fans, CR boxes, vornados, etc.
- If there is a room air cleaner present, sit as far away from it as possible. Have it not directly pointing towards you and the device.
- If using the LaminAir in an airplane, turn off the gasper above you, or at least have it pointed away from you and decrease the speed.
- If using this in a car (e.g. during a Lyft ride), close the vents near you, or at the very least, have it pointed away from the area between you and the device.
Methods¶
Production of Aerosols¶
Aqueous Solution of Salt as an Aerosol Source¶
I used Rob Wissman's instructions to create the mixture. He mentioned that in the Dal Porto, Cappa, Corsi paper, they used an aqueous salt solution of 100g / 1L to test Corsi-Rosenthal boxes. In the blog post linked in Rob's instructions, he mentioned that using regular salt has iodine, which makes it harder to aerosolize. Instead he recommends using a Kosher salt and distilled water to make the mixture as an option, and this is what I did.
\begin{equation} \begin{aligned} 100 g / L &= 100 g / L \cdot 1L \text{ }H_{2}O / 4.22675 \text{cups }H_{2}O \\ &= 100g \text{ salt} / 4.22675 \text{ cups water} \\ &= 23.658g \text{ salt} / 1 \text{ cup water} \end{aligned} \end{equation}
I didn't have access to weight scale, so I looked up the conversion of mass to volume for Kosher salt. There is 288 grams of salt per 1 cup. \begin{equation} \begin{aligned} 23.658g \text{ salt} / 1 \text{ cup water} \cdot 1 \text{ cup} / 288 g &= 0.08215 \text{ cups salt} / 1 \text{ cup water} \\ &= 0.08215 \text{ cups salt} / 1 \text{ cup water} \cdot 48 \text{ tsp} / 1 \text{ cup} \\ &= 3.9432 \text{ tsp salt} / 1 \text{ cup water} \end{aligned} \end{equation}
I rounded up and used 4 teaspoons of Kosher salt for the 1 cup of water.
Aerosolization of Salt¶
Instead of the Wellue nebulizer described in the Dal Porto, Cappa, Corsi paper paper and in Rob Wissman's instructions, I used an ultrasonic humidifier. The humidifier releases the salt solution in the air. The water evaporates, which then creates fine particulate matter, which can be suspended in the air for hours.
Detection of Aerosols¶

I used the Sensirion SPS-30 device. It gives readings of mass concentrations (micrograms per cubic meters: µg / m3) in several bins of sizes: PM 0.3-PM 1.0, PM 0.3-PM 2.5, PM 0.3-PM 4, and PM 0.3-PM 10. I used the PM 0.3-PM 1.0 for measurements because SPS30 is known to be very accurate for readings under PM 1.0 for detecting aerosolized salt. Here's a graph taken from Sousan et al, 2021, showing that SPS30 is very accurate for PM 1.0 readings across high concentrations, unlike other devices. It matches the $30,000 USD GRIMM MiniWRAS reference instrument quite well for PM 1.0:

Suite of Exercises¶
The suite of exercises I ran were mostly from OSHA to test the efficacy of the devices:
(1) Normal breathing. In a normal standing position, without talking, the subject shall breathe normally.
(2) Deep breathing. In a normal standing position, the subject shall breathe slowly and deeply, taking caution so as not to hyperventilate.
(3) Turning head side to side. Standing in place, the subject shall slowly turn his/her head from side to side between the extreme positions on each side. The head shall be held at each extreme momentarily so the subject can inhale at each side.
(4) Moving head up and down. Standing in place, the subject shall slowly move his/her head up and down. The subject shall be instructed to inhale in the up position (i.e., when looking toward the ceiling).
(5) Talking. The subject shall talk out loud slowly and loud enough so as to be heard clearly by the test conductor. The subject can read from a prepared text such as the Rainbow Passage, count backward from 100, or recite a memorized poem or song.
Rainbow Passage
When the sunlight strikes raindrops in the air, they act like a prism and form a rainbow. The rainbow is a division of white light into many beautiful colors. These take the shape of a long round arch, with its path high above, and its two ends apparently beyond the horizon. There is, according to legend, a boiling pot of gold at one end. People look, but no one ever finds it. When a man looks for something beyond reach, his friends say he is looking for the pot of gold at the end of the rainbow.
(6) Grimace. The test subject shall grimace by smiling or frowning. (This applies only to QNFT testing; it is not performed for QLFT)
(7) Bending over. The test subject shall bend at the waist as if he/she were to touch his/her toes. Jogging in place shall be substituted for this exercise in those test environments such as shroud type QNFT or QLFT units that do not permit bending over at the waist.
(8) Repeat #1
I skipped #7 and #8 for LaminAir. Number 7 doesn't apply since it's meant for someone sitting down. In theory I could have done #8 but I felt like other exercises are essentially like normal breathing, too. For example, grimace for LaminAir shouldn't actually make a difference because it doesn't depend on a tight seal to the face in order for it to work.
Use Two SPS30 Sensors to Simultaneously Measure Breathing Area and Ambient Concentrations¶
I used two sensors connected to a Raspberry Pi to simultaneously estimate the concentration in the breathing area as the LaminAir is blowing clean air to it, and the concentration in the surrounding area, around the hip, to estimate the concentration in the breathing area had the device not been turned on. See previous work for details.
Sampling Procedure & Calculations¶
- Run Humidifier and Mixing Fan for 2 minutes.
- Turn off Humidifier and let Mixing Fan run for 15 seconds.
- Turn on device (e.g. LaminAir). Wait for 15 seconds.
- Start measuring the breathing area concentration and the ambient area concentration for 1 minute.
- Turn off the device. Return to Step 1 but use 15 seconds instead.
Step 1 is to substantially increase the concentration to hundreds of micrograms per cubic meter so that we can large exposure reduction factors.
Step 2 is to see what happens when the room should be close to well-mixed. Are the two sensors reporting the same results? If not, there should be a calibration process so that the exposure reduction factors calculated actually make sense.
Step 3 is to get to the steady state concentration as reported by the sensors. It takes about 15 seconds for the readings to stabilize, probably due to the weak fan within the SPS30.
Step 4 is to measure long enough as deemed by OSHA for fit testing.
Step 5 is like Step 1 but shorter, so that we don't increase the concentrations in the air so much, just have it be stable enough across multiple exercises.
Appendix¶
Derivation of the Concentration Curve
Earlier, I claimed that the concentration in the long run is essentially the rate of dirty air generation divided by the clean air delivery rate. Below, I show how we got to this result.
The scenario we are considering is when the infector and susceptibles come in into the room at the same time, and that there were no infectors prior to the location (and therefore the initial concentration of dirty air is 0. The change in concentration over time is then dictated by the following equation:
$$\frac{dC}{dt} = \frac{-Q \cdot C_{t} + g}{V}$$
where
- $Q$ is the non-infectious air delivery rate,
- $C_t$ is the concentration at time $t$,
- $g$ is the quanta generation rate, and
- $V$ is the volume of the room.
What the above says is that some amount of air $g$ is being generated by the infector. We then divide it by the volume of the room $V$ to get the added concentration. In other words, $g / V$ is the proportion of dirty air that's being added. However, some non-infectious air is being delivered at a rate $Q$, made up of concentration $C_{t}$. In other words, $Q$ with a concentration of $C_t$ is being removed.
$$ \begin{equation} \begin{aligned} V \cdot \frac{dC}{dt} &= -Q \cdot C_t + g \\ B_t &= -Q \cdot C_t + g \\ B_0 &= -Q \cdot C_0 + g \\ \frac{dB_t}{dt} &= -Q \frac{dC_t}{dt} \\ -\frac{1}{Q} \cdot \frac{dB_t}{dt} &= \frac{dC_t}{dt} \\ -\frac{1}{Q} \cdot \frac{dB_t}{dt} &= \frac{B_t}{V} \\ -\frac{V}{Q} \cdot \frac{dB_t}{dt} &= B_t \\ -\frac{V}{Q} \cdot \frac{dB_t}{B_t} &= dt \\ \frac{dB_t}{B_t} &= -\frac{Q}{V} \cdot dt \\ \int_0^t \frac{1}{B_t} \cdot dB_t &= -\frac{Q}{V} \int_0^t dt \\ ln(B_t) - ln(B_0) &= -\frac{Q}{V} \cdot t \\ e^{ln(B_t) - ln(B_0)} &= e^{-\frac{Q}{V} \cdot t} \\ e^{ln(B_t)} /e^{ln(B_0)} &= e^{-\frac{Q}{V} \cdot t} \\ B_t / B_0 &= e^{-\frac{Q}{V} \cdot t} \\ B_t &= B_0 \cdot e^{-\frac{Q}{V} \cdot t} \\ -Q \cdot C_t + g &= (-Q \cdot C_0 + g) \cdot e^{-\frac{Q}{V} \cdot t} \\ -Q \cdot C_t &= -g + (-Q \cdot C_0 + g) \cdot e^{-\frac{Q}{V} \cdot t} \\ C_t &= g/Q + (C_0 - g/Q) \cdot e^{-\frac{Q}{V} \cdot t} \\ \end{aligned} \end{equation} $$
Given the scenario we are considering, where there were no infectors in the space before, so $C_0 = 0$, we have:
$$ \begin{equation} \begin{aligned} C_t &= g/Q - g/Q \cdot e^{-\frac{Q}{V} \cdot t} \\ C_t &= g/Q \cdot (1 - e^{-\frac{Q}{V} \cdot t}) \\ \end{aligned} \end{equation} $$
In the long run, we have:
$$ \begin{equation} \begin{aligned} lim_{t \rightarrow \infty} C_t &= g/Q \cdot lim_{t \rightarrow \infty} (1 - e^{-\frac{Q}{V} \cdot t}) \\ &= g / Q \end{aligned} \end{equation} $$
which is basically the rate of dirty air generation divided by clean air delivery rate. Thanks to Kow Akepanidtaworn for much help in this derivation.
Computations¶
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from utilities import between, plot, plot_one_graph, read_csv, \
get_fit_factor_between_two_events, get_fit_factors, read_2_sensors, hmff, bar_plot_exposure_reduction_factors, \
compute_cadr_via_anemometer_grid
dfs = read_2_sensors(template='../milestone_eoy_2023_{}.csv', columns=['pm1', 'n05'])
dfs
{'breathing_area_df': pm1 pm4 pm25 pm10 n05 n1 n25 n4 n10 \ timestamp 2023-12-15 19:45:40+00:00 0.8 8.4 5.0 10.1 0.0 1.0 5.3 6.2 6.4 2023-12-15 19:45:41+00:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 2023-12-15 19:45:42+00:00 0.4 4.2 2.5 5.0 0.0 0.5 2.6 3.1 3.2 2023-12-15 19:45:43+00:00 0.4 4.1 2.5 5.0 0.0 0.5 2.6 3.1 3.2 2023-12-15 19:45:45+00:00 0.4 4.1 2.4 4.9 0.0 0.5 2.6 3.0 3.1 ... ... ... ... ... ... ... ... ... ... 2023-12-16 03:24:54+00:00 2.6 9.9 6.7 11.5 6.7 16.1 20.1 21.0 21.2 2023-12-16 03:24:55+00:00 2.6 9.9 6.7 11.5 6.7 16.0 20.1 20.9 21.1 2023-12-16 03:24:56+00:00 2.6 9.9 6.7 11.5 6.8 16.1 20.1 21.0 21.2 2023-12-16 03:24:57+00:00 2.7 10.0 6.8 11.6 6.8 16.2 20.3 21.2 21.4 2023-12-16 03:24:58+00:00 2.7 10.2 6.9 11.8 7.0 16.6 20.8 21.7 21.9 tps pm1 breathing_area n05 breathing_area timestamp 2023-12-15 19:45:40+00:00 1.6 0.8 0.0 2023-12-15 19:45:41+00:00 1.6 0.0 0.0 2023-12-15 19:45:42+00:00 1.6 0.4 0.0 2023-12-15 19:45:43+00:00 1.6 0.4 0.0 2023-12-15 19:45:45+00:00 1.6 0.4 0.0 ... ... ... ... 2023-12-16 03:24:54+00:00 1.1 2.6 6.7 2023-12-16 03:24:55+00:00 1.1 2.6 6.7 2023-12-16 03:24:56+00:00 1.1 2.6 6.8 2023-12-16 03:24:57+00:00 1.1 2.7 6.8 2023-12-16 03:24:58+00:00 1.1 2.7 7.0 [2999 rows x 12 columns], 'ambient_area_df': pm1 pm4 pm25 pm10 n05 n1 n25 n4 \ timestamp 2023-12-15 19:45:40+00:00 2.2 8.1 5.4 9.4 6.7 13.6 17.0 17.5 2023-12-15 19:45:41+00:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 2023-12-15 19:45:42+00:00 1.2 2.3 1.9 2.6 7.0 9.2 9.8 9.9 2023-12-15 19:45:43+00:00 1.2 2.5 1.9 2.7 6.9 9.2 9.8 9.9 2023-12-15 19:45:45+00:00 1.2 2.3 1.8 2.5 7.0 9.1 9.7 9.8 ... ... ... ... ... ... ... ... ... 2023-12-16 03:24:54+00:00 2.9 8.8 6.2 10.0 11.8 19.4 22.7 23.3 2023-12-16 03:24:55+00:00 3.1 9.3 6.5 10.6 12.5 20.6 24.1 24.7 2023-12-16 03:24:57+00:00 3.2 9.6 6.8 11.0 13.0 21.4 25.0 25.6 2023-12-16 03:24:58+00:00 3.3 10.0 7.1 11.4 13.6 22.4 26.1 26.8 2023-12-16 03:24:59+00:00 3.4 10.1 7.1 11.6 13.8 22.7 26.4 27.1 n10 tps pm1 ambient_area n05 ambient_area timestamp 2023-12-15 19:45:40+00:00 17.6 1.0 2.2 6.7 2023-12-15 19:45:41+00:00 0.0 1.6 0.0 0.0 2023-12-15 19:45:42+00:00 9.9 0.7 1.2 7.0 2023-12-15 19:45:43+00:00 10.0 0.9 1.2 6.9 2023-12-15 19:45:45+00:00 9.8 0.9 1.2 7.0 ... ... ... ... ... 2023-12-16 03:24:54+00:00 23.4 1.1 2.9 11.8 2023-12-16 03:24:55+00:00 24.8 1.1 3.1 12.5 2023-12-16 03:24:57+00:00 25.8 1.1 3.2 13.0 2023-12-16 03:24:58+00:00 26.9 1.0 3.3 13.6 2023-12-16 03:24:59+00:00 27.2 1.1 3.4 13.8 [2999 rows x 12 columns]}
dfs_2 = read_2_sensors(template='../milestone_eoy_2023_part_2_{}.csv')
dfs_2
{'breathing_area_df': pm1 pm4 pm25 pm10 n05 n1 n25 n4 n10 \ timestamp 2023-12-16 03:25:00+00:00 2.7 10.3 6.9 11.9 7.1 16.7 20.9 21.8 22.0 2023-12-16 03:25:01+00:00 2.7 10.3 7.0 11.9 7.1 16.8 21.0 21.9 22.1 2023-12-16 03:25:02+00:00 2.7 10.3 7.0 12.0 7.1 16.8 21.0 21.9 22.1 2023-12-16 03:25:03+00:00 2.7 10.3 7.0 12.0 7.1 16.8 21.0 21.9 22.1 2023-12-16 03:25:04+00:00 2.7 10.2 6.9 11.9 7.0 16.6 20.8 21.7 21.9 ... ... ... ... ... ... ... ... ... ... 2023-12-16 20:17:45+00:00 1.1 3.7 2.6 4.2 4.0 7.5 8.9 9.2 9.2 2023-12-16 20:17:47+00:00 1.2 3.8 2.7 4.4 4.3 7.8 9.3 9.6 9.7 2023-12-16 20:17:48+00:00 1.2 3.9 2.7 4.4 4.3 7.9 9.4 9.7 9.8 2023-12-16 20:17:49+00:00 1.2 3.9 2.7 4.5 4.4 8.0 9.5 9.8 9.9 2023-12-16 20:17:50+00:00 1.2 3.9 2.8 4.5 4.5 8.2 9.7 10.0 10.0 tps pm1 breathing_area timestamp 2023-12-16 03:25:00+00:00 1.1 2.7 2023-12-16 03:25:01+00:00 1.1 2.7 2023-12-16 03:25:02+00:00 1.1 2.7 2023-12-16 03:25:03+00:00 1.1 2.7 2023-12-16 03:25:04+00:00 1.1 2.7 ... ... ... 2023-12-16 20:17:45+00:00 1.0 1.1 2023-12-16 20:17:47+00:00 1.0 1.2 2023-12-16 20:17:48+00:00 1.0 1.2 2023-12-16 20:17:49+00:00 1.0 1.2 2023-12-16 20:17:50+00:00 1.0 1.2 [1469 rows x 11 columns], 'ambient_area_df': pm1 pm4 pm25 pm10 n05 n1 n25 n4 \ timestamp 2023-12-16 03:25:00+00:00 3.4 10.2 7.2 11.6 13.9 22.8 26.6 27.3 2023-12-16 03:25:01+00:00 3.4 10.1 7.1 11.6 13.9 22.7 26.5 27.2 2023-12-16 03:25:02+00:00 3.4 10.0 7.1 11.5 13.9 22.6 26.4 27.0 2023-12-16 03:25:03+00:00 3.4 10.0 7.0 11.4 13.8 22.5 26.2 26.9 2023-12-16 03:25:05+00:00 3.3 9.9 7.0 11.3 13.7 22.4 26.0 26.7 ... ... ... ... ... ... ... ... ... 2023-12-16 20:17:45+00:00 1.6 4.3 3.1 4.8 7.3 11.1 12.6 12.9 2023-12-16 20:17:46+00:00 1.6 4.3 3.1 4.9 7.4 11.2 12.7 13.0 2023-12-16 20:17:47+00:00 1.6 4.3 3.1 4.9 7.5 11.3 12.8 13.1 2023-12-16 20:17:48+00:00 1.6 4.3 3.1 4.9 7.6 11.4 12.9 13.2 2023-12-16 20:17:49+00:00 1.6 4.3 3.1 4.9 7.5 11.3 12.8 13.1 n10 tps pm1 ambient_area timestamp 2023-12-16 03:25:00+00:00 27.4 1.1 3.4 2023-12-16 03:25:01+00:00 27.3 1.0 3.4 2023-12-16 03:25:02+00:00 27.2 1.0 3.4 2023-12-16 03:25:03+00:00 27.0 1.0 3.4 2023-12-16 03:25:05+00:00 26.8 1.0 3.3 ... ... ... ... 2023-12-16 20:17:45+00:00 12.9 0.9 1.6 2023-12-16 20:17:46+00:00 13.1 0.9 1.6 2023-12-16 20:17:47+00:00 13.2 0.9 1.6 2023-12-16 20:17:48+00:00 13.2 0.9 1.6 2023-12-16 20:17:49+00:00 13.1 0.9 1.6 [1467 rows x 11 columns]}
combined = {
'breathing_area_df': pd.concat([dfs['breathing_area_df'], dfs_2['breathing_area_df']]),
'ambient_area_df': pd.concat([dfs['ambient_area_df'], dfs_2['ambient_area_df']]),
}
normal_breathing_start_offset = pd.to_timedelta('15 seconds')
start_datetime = pd.to_datetime('2023-12-15 21:41:12 -05:00')
start_datetime_2 = pd.to_datetime('2023-12-15 22:11:30 -05:00')
start_datetime_3 = pd.to_datetime('2023-12-16 14:56:23 -05:00')
metadata = [
{
'title': 'Part I',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'start fan & humidifier',
'timedelta': pd.to_timedelta('0 minutes 12 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('2 minutes 0 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('2 minutes 20 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('2 minutes 34 seconds')
},
{
'event': 'AirGo - with visor - normal breathing',
'timedelta': pd.to_timedelta('2 minutes 45 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('3 minutes 46 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('4 minutes 28 seconds')
},
{
'event': 'turn off fan, turn on AirGo',
'timedelta': pd.to_timedelta('4 minutes 51 seconds')
},
]
},
{
'title': 'Part II',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'AirGo - with visor - deep breathing',
'timedelta': pd.to_timedelta('5 minutes 14 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('6 minutes 3 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('6 minutes 23 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('6 minutes 36 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('6 minutes 36 seconds')
},
{
'event': 'AirGo - with visor - grimace',
'timedelta': pd.to_timedelta('7 minutes 3 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('8 minutes 6 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('8 minutes 25 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('8 minutes 52 seconds')
},
]
},
{
'title': 'Part III',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'AirGo - with visor - talking',
'timedelta': pd.to_timedelta('9 minutes 0 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('9 minutes 56 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('10 minutes 15 seconds')
},
{
'event': 'turn off fan, turn on AirGo',
'timedelta': pd.to_timedelta('10 minutes 46 seconds')
},
{
'event': 'AirGo - with visor - bend-over',
'timedelta': pd.to_timedelta('11 minutes 8 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('12 minutes 3 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('12 minutes 27 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('12 minutes 55 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('12 minutes 55 seconds')
},
]
},
{
'title': 'Part IV',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'AirGo - with visor - side-to-side',
'timedelta': pd.to_timedelta('13 minutes 3 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('14 minutes 14 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('14 minutes 30 seconds')
},
{
'event': 'turn off fan, turn on AirGo',
'timedelta': pd.to_timedelta('14 minutes 48 seconds')
},
{
'event': 'AirGo - with visor - up-and-down',
'timedelta': pd.to_timedelta('15 minutes 0 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('16 minutes 5 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('16 minutes 30 seconds')
},
{
'event': 'turn on AirGo',
'timedelta': pd.to_timedelta('16 minutes 40 seconds')
},
]
},
{
'title': 'Part V',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'AirGo - with visor - walking',
'timedelta': pd.to_timedelta('16 minutes 53 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('17 minutes 59 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('18 minutes 7 seconds')
},
{
'event': 'turn on Laminair',
'timedelta': pd.to_timedelta('19 minutes 50 seconds')
},
{
'event': 'LaminAir - normal breathing',
'timedelta': pd.to_timedelta('20 minutes 5 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('21 minutes 5 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('21 minutes 15 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('21 minutes 50 seconds')
},
{
'event': 'turn on Laminair',
'timedelta': pd.to_timedelta('22 minutes 8 seconds')
},
]
},
{
'title': 'Part VI',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'LaminAir - talking',
'timedelta': pd.to_timedelta('22 minutes 15 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('23 minutes 13 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('23 minutes 38 seconds')
},
{
'event': 'turn on Laminair',
'timedelta': pd.to_timedelta('24 minutes 7 seconds')
},
{
'event': 'LaminAir - side-to-side',
'timedelta': pd.to_timedelta('24 minutes 33 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('25 minutes 34 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('26 minutes 3 seconds')
},
{
'event': 'AirGo & LaminAir - turn on',
'timedelta': pd.to_timedelta('26 minutes 25 seconds')
},
{
'event': 'AirGo & LaminAir - normal breathing',
'timedelta': pd.to_timedelta('26 minutes 57 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('28 minutes 3 seconds')
},
]
},
{
'title': 'Part VII',
'window': {
'start': start_datetime,
'end': start_datetime + pd.to_timedelta('30 minutes 5 seconds')
},
'events': [
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('28 minutes 29 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('28 minutes 49 seconds')
},
{
'event': 'AirGo & LaminAir - turn on',
'timedelta': pd.to_timedelta('29 minutes 0 seconds')
},
{
'event': 'AirGo & LaminAir - side-to-side',
'timedelta': pd.to_timedelta('29 minutes 10 seconds')
},
{
'event': 'AirGo & LaminAir - side-to-side - end',
'timedelta': pd.to_timedelta('30 minutes 5 seconds')
}
]
},
{
'title': 'Part VII',
'window': {
'start': start_datetime_2,
'end': start_datetime_2 + pd.to_timedelta('2 minutes 4 seconds')
},
'events': [
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('0 minutes 18 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('0 minutes 28 seconds')
},
{
'event': 'AirGo & LaminAir - turn on',
'timedelta': pd.to_timedelta('0 minutes 44 seconds')
},
{
'event': 'AirGo & LaminAir - talking',
'timedelta': pd.to_timedelta('0 minutes 57 seconds')
},
{
'event': 'AirGo & LaminAir - talking end',
'timedelta': pd.to_timedelta('2 minutes 4 seconds')
}
]
},
{
'title': 'Part VIII',
'window': {
'start': start_datetime_3,
'end': start_datetime_3 + pd.to_timedelta('17 minutes 34 seconds')
},
'events': [
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('0 minutes 4 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('2 minutes 0 seconds')
},
{
'event': 'LaminAir - turn on',
'timedelta': pd.to_timedelta('2 minutes 30 seconds')
},
{
'event': 'LaminAir - deep breathing',
'timedelta': pd.to_timedelta('2 minutes 45 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('4 minutes 4 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('4 minutes 4 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('4 minutes 30 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('4 minutes 54 seconds')
},
{
'event': 'LaminAir - turn on',
'timedelta': pd.to_timedelta('4 minutes 59 seconds')
},
{
'event': 'LaminAir - grimace',
'timedelta': pd.to_timedelta('5 minutes 18 seconds')
},
{
'event': 'LaminAir - grimace - end',
'timedelta': pd.to_timedelta('6 minutes 8 seconds')
},
]
},
{
'title': 'Part IX',
'window': {
'start': start_datetime_3,
'end': start_datetime_3 + pd.to_timedelta('17 minutes 34 seconds')
},
'events': [
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('6 minutes 8 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('6 minutes 35 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('6 minutes 52 seconds')
},
{
'event': 'LaminAir - turn on',
'timedelta': pd.to_timedelta('7 minutes 3 seconds')
},
{
'event': 'LaminAir - up-and-down',
'timedelta': pd.to_timedelta('7 minutes 21 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('8 minutes 19 seconds')
},
{
'event': 'LaminAir - turn on',
'timedelta': pd.to_timedelta('9 minutes 16 seconds')
},
{
'event': 'AirGo & LaminAir - deep breathing',
'timedelta': pd.to_timedelta('9 minutes 29 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('10 minutes 33 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('11 minutes 6 seconds')
},
{
'event': 'AirGo & LaminAir - turn on',
'timedelta': pd.to_timedelta('11 minutes 27 seconds')
},
]
},
{
'title': 'Part X',
'window': {
'start': start_datetime_3,
'end': start_datetime_3 + pd.to_timedelta('17 minutes 34 seconds')
},
'events': [
{
'event': 'AirGo & LaminAir - grimace',
'timedelta': pd.to_timedelta('11 minutes 46 seconds')
},
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('12 minutes 52 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('13 minutes 29 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('13 minutes 47 seconds')
},
# {
# 'event': 'AirGo & LaminAir - side-to-side',
# 'timedelta': pd.to_timedelta('14 minutes 0 seconds')
# },
{
'event': 'turn on fan and humidifier',
'timedelta': pd.to_timedelta('15 minutes 7 seconds')
},
{
'event': 'turn off humidifier',
'timedelta': pd.to_timedelta('15 minutes 42 seconds')
},
{
'event': 'turn off fan',
'timedelta': pd.to_timedelta('16 minutes 0 seconds')
},
{
'event': 'AirGo & LaminAir - turn on',
'timedelta': pd.to_timedelta('16 minutes 0 seconds')
},
{
'event': 'AirGo & LaminAir - up-and-down',
'timedelta': pd.to_timedelta('16 minutes 30 seconds')
},
{
'event': 'AirGo & LaminAir - up-and-down',
'timedelta': pd.to_timedelta('17 minutes 34 seconds')
}
]
},
]
Plots¶
Mass Concentration:
plot(
metadata,
breathing_area_data=combined['breathing_area_df'],
ambient_data=combined['ambient_area_df'],
breathing_area_vars=['pm1 breathing_area'],
ambient_vars=['pm1 ambient_area'],
row_size=4,
)
fit_factors = get_fit_factors(
[
{
'metadata': metadata,
'breathing_area_sensor_data': combined['breathing_area_df'],
'ambient_sensor_data': combined['ambient_area_df']
}
],
title='graph',
breathing_area_column='pm1 breathing_area',
ambient_column='pm1'
)
/Users/eugaddan/Developer/iaq/utilities.py:120: RuntimeWarning: invalid value encountered in scalar divide return numerator / denominator
fit_factors
graph | event | fit_factor | |
---|---|---|---|
0 | Part I | start fan & humidifier | 1.864577 |
1 | Part I | turn off humidifier | 1.572468 |
2 | Part I | turn off fan | 2.729688 |
3 | Part I | turn on AirGo | 16.941429 |
4 | Part I | AirGo - with visor - normal breathing | 14.341690 |
... | ... | ... | ... |
79 | Part X | turn on fan and humidifier | 1.727325 |
80 | Part X | turn off humidifier | 1.347302 |
81 | Part X | turn off fan | NaN |
82 | Part X | AirGo & LaminAir - turn on | 5.107849 |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 |
84 rows × 3 columns
no_na = fit_factors[fit_factors['fit_factor'].notna()].copy()
no_na
graph | event | fit_factor | |
---|---|---|---|
0 | Part I | start fan & humidifier | 1.864577 |
1 | Part I | turn off humidifier | 1.572468 |
2 | Part I | turn off fan | 2.729688 |
3 | Part I | turn on AirGo | 16.941429 |
4 | Part I | AirGo - with visor - normal breathing | 14.341690 |
... | ... | ... | ... |
78 | Part X | turn off fan | 14.433278 |
79 | Part X | turn on fan and humidifier | 1.727325 |
80 | Part X | turn off humidifier | 1.347302 |
82 | Part X | AirGo & LaminAir - turn on | 5.107849 |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 |
80 rows × 3 columns
Calibration¶
After turning on the humidifier and the mixing fan, while the device(s) such as the AirGo and LaminAir are turned off, we should expect a fit factor of 1. In other words, when the air is well mixed, the concentration in the breathing area should be the same as the concentration in any other area. However, we don't see this pattern. We see that the concentration in the breathing area is consistently smaller than the concentration in the ambient area. This suggests that there is a statistical bias that needs to be corrected for so that the interpretation of the fit factor is correct. Below, we can see that the fit factor is about 1.4 - 1.6 when it really should be about 1. We'll divide the fit factors by this value to adjust for this discrepancy.
no_na[no_na['event'].str.contains('off humidifier')]
graph | event | fit_factor | |
---|---|---|---|
1 | Part I | turn off humidifier | 1.572468 |
6 | Part I | turn off humidifier | 1.605354 |
9 | Part II | turn off humidifier | 1.574754 |
14 | Part II | turn off humidifier | 1.346522 |
17 | Part III | turn off humidifier | 1.489655 |
21 | Part III | turn off humidifier | 1.586929 |
25 | Part IV | turn off humidifier | 1.480119 |
29 | Part IV | turn off humidifier | 1.400237 |
32 | Part V | turn off humidifier | 1.430543 |
36 | Part V | turn off humidifier | 1.462251 |
40 | Part VI | turn off humidifier | 1.405388 |
44 | Part VI | turn off humidifier | 1.385183 |
47 | Part VII | turn off humidifier | 1.405595 |
52 | Part VII | turn off humidifier | 1.414311 |
56 | Part VIII | turn off humidifier | 1.392232 |
61 | Part VIII | turn off humidifier | 1.293907 |
66 | Part IX | turn off humidifier | 1.383300 |
74 | Part IX | turn off humidifier | 1.383234 |
77 | Part X | turn off humidifier | 1.365158 |
80 | Part X | turn off humidifier | 1.347302 |
no_na[no_na['event'].str.contains('off humidifier')]['fit_factor'].plot.hist()
<Axes: ylabel='Frequency'>
no_na['adjusted fit factor 1'] = no_na['fit_factor'] / 1.4
no_na['adjusted fit factor 2'] = no_na['fit_factor'] / 1.6
results = no_na[no_na['event'].str.contains('AirGo|LaminAir') & ~no_na['event'].str.contains('turn')].copy()
results
graph | event | fit_factor | adjusted fit factor 1 | adjusted fit factor 2 | |
---|---|---|---|---|---|
4 | Part I | AirGo - with visor - normal breathing | 14.341690 | 10.244064 | 8.963556 |
7 | Part II | AirGo - with visor - deep breathing | 16.990390 | 12.135993 | 10.618994 |
12 | Part II | AirGo - with visor - grimace | 12.048281 | 8.605915 | 7.530176 |
15 | Part III | AirGo - with visor - talking | 11.442173 | 8.172981 | 7.151358 |
19 | Part III | AirGo - with visor - bend-over | 14.652330 | 10.465950 | 9.157706 |
23 | Part IV | AirGo - with visor - side-to-side | 1.696143 | 1.211530 | 1.060089 |
27 | Part IV | AirGo - with visor - up-and-down | 3.320207 | 2.371577 | 2.075129 |
30 | Part V | AirGo - with visor - walking | 4.825042 | 3.446459 | 3.015652 |
34 | Part V | LaminAir - normal breathing | 27.692461 | 19.780329 | 17.307788 |
38 | Part VI | LaminAir - talking | 184.936287 | 132.097348 | 115.585179 |
42 | Part VI | LaminAir - side-to-side | 18.587357 | 13.276684 | 11.617098 |
46 | Part VI | AirGo & LaminAir - normal breathing | 82.561398 | 58.972427 | 51.600874 |
50 | Part VII | AirGo & LaminAir - side-to-side | 72.862997 | 52.044998 | 45.539373 |
54 | Part VII | AirGo & LaminAir - talking | 203.132099 | 145.094356 | 126.957562 |
58 | Part VIII | LaminAir - deep breathing | 122.477090 | 87.483636 | 76.548181 |
64 | Part VIII | LaminAir - grimace | 138.285520 | 98.775371 | 86.428450 |
69 | Part IX | LaminAir - up-and-down | 132.497512 | 94.641080 | 82.810945 |
72 | Part IX | AirGo & LaminAir - deep breathing | 255.226344 | 182.304531 | 159.516465 |
75 | Part X | AirGo & LaminAir - grimace | 113.663963 | 81.188545 | 71.039977 |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 | 203.038730 | 177.658889 |
results
graph | event | fit_factor | adjusted fit factor 1 | adjusted fit factor 2 | |
---|---|---|---|---|---|
4 | Part I | AirGo - with visor - normal breathing | 14.341690 | 10.244064 | 8.963556 |
7 | Part II | AirGo - with visor - deep breathing | 16.990390 | 12.135993 | 10.618994 |
12 | Part II | AirGo - with visor - grimace | 12.048281 | 8.605915 | 7.530176 |
15 | Part III | AirGo - with visor - talking | 11.442173 | 8.172981 | 7.151358 |
19 | Part III | AirGo - with visor - bend-over | 14.652330 | 10.465950 | 9.157706 |
23 | Part IV | AirGo - with visor - side-to-side | 1.696143 | 1.211530 | 1.060089 |
27 | Part IV | AirGo - with visor - up-and-down | 3.320207 | 2.371577 | 2.075129 |
30 | Part V | AirGo - with visor - walking | 4.825042 | 3.446459 | 3.015652 |
34 | Part V | LaminAir - normal breathing | 27.692461 | 19.780329 | 17.307788 |
38 | Part VI | LaminAir - talking | 184.936287 | 132.097348 | 115.585179 |
42 | Part VI | LaminAir - side-to-side | 18.587357 | 13.276684 | 11.617098 |
46 | Part VI | AirGo & LaminAir - normal breathing | 82.561398 | 58.972427 | 51.600874 |
50 | Part VII | AirGo & LaminAir - side-to-side | 72.862997 | 52.044998 | 45.539373 |
54 | Part VII | AirGo & LaminAir - talking | 203.132099 | 145.094356 | 126.957562 |
58 | Part VIII | LaminAir - deep breathing | 122.477090 | 87.483636 | 76.548181 |
64 | Part VIII | LaminAir - grimace | 138.285520 | 98.775371 | 86.428450 |
69 | Part IX | LaminAir - up-and-down | 132.497512 | 94.641080 | 82.810945 |
72 | Part IX | AirGo & LaminAir - deep breathing | 255.226344 | 182.304531 | 159.516465 |
75 | Part X | AirGo & LaminAir - grimace | 113.663963 | 81.188545 | 71.039977 |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 | 203.038730 | 177.658889 |
results['exercises'] = results['event'].str.extract('(?<=AirGo - with visor - )(.*)')[0]
results
graph | event | fit_factor | adjusted fit factor 1 | adjusted fit factor 2 | exercises | |
---|---|---|---|---|---|---|
4 | Part I | AirGo - with visor - normal breathing | 14.341690 | 10.244064 | 8.963556 | normal breathing |
7 | Part II | AirGo - with visor - deep breathing | 16.990390 | 12.135993 | 10.618994 | deep breathing |
12 | Part II | AirGo - with visor - grimace | 12.048281 | 8.605915 | 7.530176 | grimace |
15 | Part III | AirGo - with visor - talking | 11.442173 | 8.172981 | 7.151358 | talking |
19 | Part III | AirGo - with visor - bend-over | 14.652330 | 10.465950 | 9.157706 | bend-over |
23 | Part IV | AirGo - with visor - side-to-side | 1.696143 | 1.211530 | 1.060089 | side-to-side |
27 | Part IV | AirGo - with visor - up-and-down | 3.320207 | 2.371577 | 2.075129 | up-and-down |
30 | Part V | AirGo - with visor - walking | 4.825042 | 3.446459 | 3.015652 | walking |
34 | Part V | LaminAir - normal breathing | 27.692461 | 19.780329 | 17.307788 | NaN |
38 | Part VI | LaminAir - talking | 184.936287 | 132.097348 | 115.585179 | NaN |
42 | Part VI | LaminAir - side-to-side | 18.587357 | 13.276684 | 11.617098 | NaN |
46 | Part VI | AirGo & LaminAir - normal breathing | 82.561398 | 58.972427 | 51.600874 | NaN |
50 | Part VII | AirGo & LaminAir - side-to-side | 72.862997 | 52.044998 | 45.539373 | NaN |
54 | Part VII | AirGo & LaminAir - talking | 203.132099 | 145.094356 | 126.957562 | NaN |
58 | Part VIII | LaminAir - deep breathing | 122.477090 | 87.483636 | 76.548181 | NaN |
64 | Part VIII | LaminAir - grimace | 138.285520 | 98.775371 | 86.428450 | NaN |
69 | Part IX | LaminAir - up-and-down | 132.497512 | 94.641080 | 82.810945 | NaN |
72 | Part IX | AirGo & LaminAir - deep breathing | 255.226344 | 182.304531 | 159.516465 | NaN |
75 | Part X | AirGo & LaminAir - grimace | 113.663963 | 81.188545 | 71.039977 | NaN |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 | 203.038730 | 177.658889 | NaN |
results['exercises'] = results['exercises'].mask(
results['event'].str.extract('(?<=LaminAir - )(.*)')[0].notna(),
results['event'].str.extract('(?<=LaminAir - )(.*)')[0]
)
results
graph | event | fit_factor | adjusted fit factor 1 | adjusted fit factor 2 | exercises | |
---|---|---|---|---|---|---|
4 | Part I | AirGo - with visor - normal breathing | 14.341690 | 10.244064 | 8.963556 | normal breathing |
7 | Part II | AirGo - with visor - deep breathing | 16.990390 | 12.135993 | 10.618994 | deep breathing |
12 | Part II | AirGo - with visor - grimace | 12.048281 | 8.605915 | 7.530176 | grimace |
15 | Part III | AirGo - with visor - talking | 11.442173 | 8.172981 | 7.151358 | talking |
19 | Part III | AirGo - with visor - bend-over | 14.652330 | 10.465950 | 9.157706 | bend-over |
23 | Part IV | AirGo - with visor - side-to-side | 1.696143 | 1.211530 | 1.060089 | side-to-side |
27 | Part IV | AirGo - with visor - up-and-down | 3.320207 | 2.371577 | 2.075129 | up-and-down |
30 | Part V | AirGo - with visor - walking | 4.825042 | 3.446459 | 3.015652 | walking |
34 | Part V | LaminAir - normal breathing | 27.692461 | 19.780329 | 17.307788 | normal breathing |
38 | Part VI | LaminAir - talking | 184.936287 | 132.097348 | 115.585179 | talking |
42 | Part VI | LaminAir - side-to-side | 18.587357 | 13.276684 | 11.617098 | side-to-side |
46 | Part VI | AirGo & LaminAir - normal breathing | 82.561398 | 58.972427 | 51.600874 | normal breathing |
50 | Part VII | AirGo & LaminAir - side-to-side | 72.862997 | 52.044998 | 45.539373 | side-to-side |
54 | Part VII | AirGo & LaminAir - talking | 203.132099 | 145.094356 | 126.957562 | talking |
58 | Part VIII | LaminAir - deep breathing | 122.477090 | 87.483636 | 76.548181 | deep breathing |
64 | Part VIII | LaminAir - grimace | 138.285520 | 98.775371 | 86.428450 | grimace |
69 | Part IX | LaminAir - up-and-down | 132.497512 | 94.641080 | 82.810945 | up-and-down |
72 | Part IX | AirGo & LaminAir - deep breathing | 255.226344 | 182.304531 | 159.516465 | deep breathing |
75 | Part X | AirGo & LaminAir - grimace | 113.663963 | 81.188545 | 71.039977 | grimace |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 | 203.038730 | 177.658889 | up-and-down |
results['model'] = results['event'].str.extract('(.*)(?= -)')[0]
results
graph | event | fit_factor | adjusted fit factor 1 | adjusted fit factor 2 | exercises | model | |
---|---|---|---|---|---|---|---|
4 | Part I | AirGo - with visor - normal breathing | 14.341690 | 10.244064 | 8.963556 | normal breathing | AirGo - with visor |
7 | Part II | AirGo - with visor - deep breathing | 16.990390 | 12.135993 | 10.618994 | deep breathing | AirGo - with visor |
12 | Part II | AirGo - with visor - grimace | 12.048281 | 8.605915 | 7.530176 | grimace | AirGo - with visor |
15 | Part III | AirGo - with visor - talking | 11.442173 | 8.172981 | 7.151358 | talking | AirGo - with visor |
19 | Part III | AirGo - with visor - bend-over | 14.652330 | 10.465950 | 9.157706 | bend-over | AirGo - with visor |
23 | Part IV | AirGo - with visor - side-to-side | 1.696143 | 1.211530 | 1.060089 | side-to-side | AirGo - with visor |
27 | Part IV | AirGo - with visor - up-and-down | 3.320207 | 2.371577 | 2.075129 | up-and-down | AirGo - with visor |
30 | Part V | AirGo - with visor - walking | 4.825042 | 3.446459 | 3.015652 | walking | AirGo - with visor |
34 | Part V | LaminAir - normal breathing | 27.692461 | 19.780329 | 17.307788 | normal breathing | LaminAir |
38 | Part VI | LaminAir - talking | 184.936287 | 132.097348 | 115.585179 | talking | LaminAir |
42 | Part VI | LaminAir - side-to-side | 18.587357 | 13.276684 | 11.617098 | side-to-side | LaminAir |
46 | Part VI | AirGo & LaminAir - normal breathing | 82.561398 | 58.972427 | 51.600874 | normal breathing | AirGo & LaminAir |
50 | Part VII | AirGo & LaminAir - side-to-side | 72.862997 | 52.044998 | 45.539373 | side-to-side | AirGo & LaminAir |
54 | Part VII | AirGo & LaminAir - talking | 203.132099 | 145.094356 | 126.957562 | talking | AirGo & LaminAir |
58 | Part VIII | LaminAir - deep breathing | 122.477090 | 87.483636 | 76.548181 | deep breathing | LaminAir |
64 | Part VIII | LaminAir - grimace | 138.285520 | 98.775371 | 86.428450 | grimace | LaminAir |
69 | Part IX | LaminAir - up-and-down | 132.497512 | 94.641080 | 82.810945 | up-and-down | LaminAir |
72 | Part IX | AirGo & LaminAir - deep breathing | 255.226344 | 182.304531 | 159.516465 | deep breathing | AirGo & LaminAir |
75 | Part X | AirGo & LaminAir - grimace | 113.663963 | 81.188545 | 71.039977 | grimace | AirGo & LaminAir |
83 | Part X | AirGo & LaminAir - up-and-down | 284.254222 | 203.038730 | 177.658889 | up-and-down | AirGo & LaminAir |
pivot = results.pivot(
index='exercises',
columns='model',
values='adjusted fit factor 1'
)
pivot.apply(hmff)
model AirGo & LaminAir 91.198174 AirGo - with visor 3.899901 LaminAir 36.239239 dtype: float64
transpose = pd.DataFrame(pivot.apply(hmff)).T
transpose['model'] = 'overall'
overall_fit_factor_df = transpose.reset_index().set_index('model')[['AirGo & LaminAir', 'AirGo - with visor', 'LaminAir']]
pivot_with_overall_fit_factor = pd.concat([pivot, overall_fit_factor_df])
Exposure Reduction Factors of LaminAir, AirGo prototype (with visor), and combined¶
fig, ax = plt.subplots(1,1, figsize=(10,5))
sns.heatmap(pivot_with_overall_fit_factor, annot=True, fmt='g', ax=ax)
ax.set_title("Exposure Reduction Factors (mass concentration)")
Text(0.5, 1.0, 'Exposure Reduction Factors (mass concentration)')
pivot
model | AirGo & LaminAir | AirGo - with visor | LaminAir |
---|---|---|---|
exercises | |||
bend-over | NaN | 10.465950 | NaN |
deep breathing | 182.304531 | 12.135993 | 87.483636 |
grimace | 81.188545 | 8.605915 | 98.775371 |
normal breathing | 58.972427 | 10.244064 | 19.780329 |
side-to-side | 52.044998 | 1.211530 | 13.276684 |
talking | 145.094356 | 8.172981 | 132.097348 |
up-and-down | 203.038730 | 2.371577 | 94.641080 |
walking | NaN | 3.446459 | NaN |
LaminAir Performance Over a Suite of Exercises¶
blah = pivot_with_overall_fit_factor[['LaminAir']]
blah[blah.index == 'overall']
model | LaminAir |
---|---|
overall | 36.239239 |
fig, ax = plt.subplots(figsize=(10,5))
laminair = pivot_with_overall_fit_factor[['LaminAir']].dropna().sort_values(by="LaminAir")
laminair.plot.bar(ax=ax)
ax.set_ylabel("Exposure Reduction Factor")
ax.set_title("LaminAir performance over different exercises")
ax.axhline(20, color='r')
ax.axhline(100, color='g')
plt.xticks(rotation=45)
ax.legend(['Lower Bound Exposure Reduction Factor for N95, no leakage', 'Typical Exposure Reduction Factor for N95, no leakage'])
#laminair[laminair.index == 'overall'].plot.bar(color='r', ax=ax)
<matplotlib.legend.Legend at 0x1327843d0>
pivot
model | AirGo & LaminAir | AirGo - with visor | LaminAir |
---|---|---|---|
exercises | |||
bend-over | NaN | 10.465950 | NaN |
deep breathing | 182.304531 | 12.135993 | 87.483636 |
grimace | 81.188545 | 8.605915 | 98.775371 |
normal breathing | 58.972427 | 10.244064 | 19.780329 |
side-to-side | 52.044998 | 1.211530 | 13.276684 |
talking | 145.094356 | 8.172981 | 132.097348 |
up-and-down | 203.038730 | 2.371577 | 94.641080 |
walking | NaN | 3.446459 | NaN |
fit_factor_context = pd.DataFrame([
{
'protection': 'well-fitted cloth mask',
'exposure reduction factor': 2,
'relative reduction': '50%',
'reference link': 'https://docs.google.com/spreadsheets/d/16K1OQkLD4BjgBdO8ePj6ytf-RpPMlJ6aXFg3PrIQBbQ/edit#gid=519189277',
'notes': 'Jose Luis-Jimenez et. al claim that the range is between 30%-50% reduction for inhaled aerosols when wearing a cloth mask,' +
' with the upper range more for those that do not have gaps between the mask and the face.',
'image_url': 'https://d.newsweek.com/en/full/1713771/biden-masks-covid-19.png'
},
# {
# 'protection': 'N95 with no leaks lower bound',
# 'exposure reduction factor': 20,
# 'relative reduction': '>= 95%',
# 'reference link': 'https://www.cdc.gov/niosh/npptl/topics/respirators/disp_part/default.html',
# 'notes': 'NIOSH',
# 'image_url': 'https://www.iceclasses.com/wp-content/uploads/2020/05/qualitive-fit-test.jpg'
# },
{
'protection': 'N95 with leakage',
'exposure reduction factor': 10,
'relative reduction': '90%',
'reference link': 'https://docs.google.com/spreadsheets/d/16K1OQkLD4BjgBdO8ePj6ytf-RpPMlJ6aXFg3PrIQBbQ/edit#gid=519189277',
'notes': 'Jose Luis-Jimenez & Linsey Marr claim that wearing an N95 with some gaps between the face and the respirator decreases'
+ ' the relative reduction to about 90%, though that might be generous for some. This is typical for most users.',
'image_url': 'https://aidwaycares.com/cdn/shop/products/9502B3_1024x1024@2x.jpg?v=1646244381'
},
# {
# 'protection': 'Typical N95 with no leakage',
# 'exposure reduction factor': 100,
# 'relative reduction': '99%',
# 'reference link': 'https://docs.google.com/spreadsheets/d/16K1OQkLD4BjgBdO8ePj6ytf-RpPMlJ6aXFg3PrIQBbQ/edit#gid=519189277',
# 'notes': 'Jose Luis-Jimenez & Linsey Marr claim that most N95s are around the 99% range, assuming no leakage.',
# 'image_url': 'https://shop.demetech.us/cdn/shop/products/MINIATURACIRCULODT-N95-CH2.png?v=1637259871'
# },
{
'protection': 'Universal cloth masking',
'exposure reduction factor': 4,
'relative reduction': '75%',
'reference link': 'https://blogs.cdc.gov/niosh-science-blog/2023/02/03/diy-filtration/',
'notes': "NIOSH researchers find that when the source uses a cloth mask and the susceptibles also use a cloth mask, we get a multiplicative protective effect"
+ " that aligns with Jose Luis Jimenez's model. The original aerosol amount got reduced by 50% via the cloth mask of the source, which then got further "
+ " reduced by another 50% via the cloth mask of the susceptible. This led to a 75% reduction, or a fit factor of 4.",
'image_url': 'https://goldarrowcamp.com/app/uploads/2020/05/IMG_1473-825x510.jpeg'
},
{
'protection': '2 Corsi-Rosenthal Boxes running on max, added to 200 CFM ventilation',
'exposure reduction factor': 5,
'relative reduction': '80%',
'reference link': 'https://www.sciencedirect.com/science/article/pii/S0360132322011507#sec2',
'notes': 'Adding an additional 1311 CFM of clean air to a classroom with 200 CFM ventilation (1.89 ACH) led to an average of 80% reduction of inhaled aerosols across 3 sensors.'
+ ' The 1311 CFM came from 2 CR boxesmade up of Air King model 4CH71G/9723G as the fan, '
+ 'and AIR HANDLER Pleated Air Filter: 20x20x2, MERV 13 (Grainger) as the filters ',
'image_url': 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGDhR8zBENToo83usQCUIBxeR9HHj5OU5QaQ&usqp=CAU'
},
{
'protection': '2 Corsi-Rosenthal Boxes running on max + Universal cloth-masking, added to 200 CFM ventilation',
'exposure reduction factor': 16.67,
'relative reduction': '94%',
'reference link': 'https://www.sciencedirect.com/science/article/pii/S0360132322011507#sec2',
'notes': 'Adding an additional 1300 CFM of clean air to a classroom with 200 CFM ventilation (1.89 ACH) led to an 80% reduction using the Air King fan and Grainger filters for CR boxes.'
+ ' When combined with universal masking with 3-ply cloth masks, aerosols were reduced by 94%. This makes sense from a modeling perspective. '
+ 'Assuming a 50% reduction via the source 3-ply cloth mask, the original amount gets reduced by 50%. '
+ '80% reduction of the remaining 50% means that 20% of 50% is left over, which is 10% of the original. '
+ 'That gets further reduced by 50% through the 3-ply cloth mask on the mannequin representing the susceptible, '
+ 'leading to 5% of the original. i.e. 95% reduction, which is close to the experimental result of 94% reduction.',
'image_url': 'https://breathesafe-airgo.s3.us-east-2.amazonaws.com/images/2-CR-boxes-and-universal-masking.png'
},
{
'protection': 'LaminAir, 12-inches away',
'exposure reduction factor': 36,
'relative reduction': '97.3%',
'reference link': '',
'image_url': 'https://breathesafe-airgo.s3.us-east-2.amazonaws.com/images/laminair/front.png'
},
{
'protection': 'QT3 on Max Speed, 12-inches away',
'exposure reduction factor': 2.5,
'relative reduction': '60%',
'reference link': 'https://smartairfilters.com/en/blog/qt3-portable-air-purifier-effectiveness/',
'image_url': 'https://www.smarterhepa.com/cdn/shop/products/shipin040101_1512x.jpg',
'notes': "FitTestThePlanet's estimate was even lower than SmartAir's own testing. See https://twitter.com/FitTestMyPlanet/status/1685793478918164480/photo/1"
},
])
sorted_ff = fit_factor_context.sort_values(by='exposure reduction factor')
copy_ff = sorted_ff.copy()
copy_ff['exposure reduction factor'] = 0
copy_ff.loc[copy_ff['protection'] == 'LaminAir, 12-inches away', 'exposure reduction factor'] = 36
merge = sorted_ff.merge(copy_ff, on='protection', suffixes=('_actual', '_coloring'))
merge
protection | exposure reduction factor_actual | relative reduction_actual | reference link_actual | notes_actual | image_url_actual | exposure reduction factor_coloring | relative reduction_coloring | reference link_coloring | notes_coloring | image_url_coloring | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | well-fitted cloth mask | 2.00 | 50% | https://docs.google.com/spreadsheets/d/16K1OQk... | Jose Luis-Jimenez et. al claim that the range ... | https://d.newsweek.com/en/full/1713771/biden-m... | 0 | 50% | https://docs.google.com/spreadsheets/d/16K1OQk... | Jose Luis-Jimenez et. al claim that the range ... | https://d.newsweek.com/en/full/1713771/biden-m... |
1 | QT3 on Max Speed, 12-inches away | 2.50 | 60% | https://smartairfilters.com/en/blog/qt3-portab... | FitTestThePlanet's estimate was even lower tha... | https://www.smarterhepa.com/cdn/shop/products/... | 0 | 60% | https://smartairfilters.com/en/blog/qt3-portab... | FitTestThePlanet's estimate was even lower tha... | https://www.smarterhepa.com/cdn/shop/products/... |
2 | Universal cloth masking | 4.00 | 75% | https://blogs.cdc.gov/niosh-science-blog/2023/... | NIOSH researchers find that when the source us... | https://goldarrowcamp.com/app/uploads/2020/05/... | 0 | 75% | https://blogs.cdc.gov/niosh-science-blog/2023/... | NIOSH researchers find that when the source us... | https://goldarrowcamp.com/app/uploads/2020/05/... |
3 | 2 Corsi-Rosenthal Boxes running on max, added ... | 5.00 | 80% | https://www.sciencedirect.com/science/article/... | Adding an additional 1311 CFM of clean air to ... | https://encrypted-tbn0.gstatic.com/images?q=tb... | 0 | 80% | https://www.sciencedirect.com/science/article/... | Adding an additional 1311 CFM of clean air to ... | https://encrypted-tbn0.gstatic.com/images?q=tb... |
4 | N95 with leakage | 10.00 | 90% | https://docs.google.com/spreadsheets/d/16K1OQk... | Jose Luis-Jimenez & Linsey Marr claim that wea... | https://aidwaycares.com/cdn/shop/products/9502... | 0 | 90% | https://docs.google.com/spreadsheets/d/16K1OQk... | Jose Luis-Jimenez & Linsey Marr claim that wea... | https://aidwaycares.com/cdn/shop/products/9502... |
5 | 2 Corsi-Rosenthal Boxes running on max + Unive... | 16.67 | 94% | https://www.sciencedirect.com/science/article/... | Adding an additional 1300 CFM of clean air to ... | https://breathesafe-airgo.s3.us-east-2.amazona... | 0 | 94% | https://www.sciencedirect.com/science/article/... | Adding an additional 1300 CFM of clean air to ... | https://breathesafe-airgo.s3.us-east-2.amazona... |
6 | LaminAir, 12-inches away | 36.00 | 97.3% | NaN | https://breathesafe-airgo.s3.us-east-2.amazona... | 36 | 97.3% | NaN | https://breathesafe-airgo.s3.us-east-2.amazona... |
results = bar_plot_exposure_reduction_factors(
fit_factor_context.sort_values(by='exposure reduction factor'),
img_func=lambda fit_factor, i: [ i / 9 + 1.2 / 9.5 , fit_factor / 150, 0.08,0.4],
figsize=(15,8),
ylim=(0, 110)
)
results['ax'].axhline(20, color='r')
results['ax'].axhline(100, color='g')
results['ax'].legend([ 'N95 without leaks, lower bound', 'N95 without leaks, typical', 'exposure reduction factor'], loc='upper center')
#merge.sort_values(by='exposure reduction factor_coloring').plot.bar(ax =results['ax'], color='g')
<matplotlib.legend.Legend at 0x132a69030>
Anemometer Grid Measurements to Compute Clean Air Delivery Rate¶
Since the LaminAir uses a HEPA, the amount of clean air being put out is essentially the clean air delivery rate. So we just need to compute how much air is being put out by the device (measured in volume of air per time). Measurements of velocity (m/s) were taken using a Kestrel Anemometer.
anemometer_grid_measurements = pd.DataFrame([
[0.7, 0.7, 0.6, 0.8, 0.6, 0.8, 0.8],
[0.8, 0.4, 0.6, 0.8, 0.6, 0.8, 0.8],
[0.9, 0.5, 0.7, 0.7, 0.7, 0.7, 0.8],
[1, 0.8, 0.8, 0.8, 0.7, 0.8, 0.8],
[1.1, 0.8, 0.6, 0.7, 0.7, 0.8, 0.7],
[1.1, 0.7, 0.7, 0.7, 0.6, 0.5, 0.6],
[0.9, 0.6, 0.8, 0.7, 0.6, 0.5, 0.7]
])
compute_cadr_via_anemometer_grid(velocity_grid_mps=anemometer_grid_measurements, filter_width_mm=240, filter_length_mm=240)
{'cubic_meters_per_hour': 150.65338775510202, 'cubic_feet_per_min': 88.67123636372374}