[R] Using na.omit() on the output of na.omit()
Shu Fai Cheung
@hu|@|@cheung @end|ng |rom gm@||@com
Mon Mar 30 11:17:53 CEST 2026
Hi,
Thanks for your comments. I will think over them.
One quick correction:
> Yes, they were. You are running na.omit on the original DF and na.action
correctly reports one case omited.
Sorry. My bad. I copied and pasted the wrong code. This is the correct example:
> DF <- data.frame(x = c(1, 2, 3, 5), y = c(NA, 10, 1, 10))
> DF_omitted <- na.omit(DF) # One case omitted in this call
> na.action(DF_omitted)
1
1
attr(,"class")
[1] "omit"
> DF_omitted2 <- na.omit(DF_omitted) # No cases removed in this call
> na.action(DF_omitted2)
1
1
attr(,"class")
[1] "omit"
I initially also thought that this was intended. However, when
compared to the behavior of lm() in the examples, I am not sure. They
give different messages to users.
In the second sample, with the dataset being the output of na.omit(),
lm() does not report that cases have been (further) removed.
However, if the dataset is the output of na.omit(), using na.omit() on
it again "suggests" that cases were removed by *this call*. It does
not really "say so" because it just returns the object unchanged, as
expected and implied in the help page. However, given the following
from the help page, it is natural for users, at least for me, to
assume that the results of na.action() are about the second call of
na.omit():
'If na.omit removes cases, the row numbers of the cases form the
"na.action" attribute of the result, of class "omit".'
I thought that way (incorrectly) because the na.action attribute is in
the output of the second call. However, unless we check an object
before calling na.omit(), or compare the input of na.omit() and its
output, there is no way to know whether the results of na.action() are
from this call of na.omit(), or from a previous one, if any.
> > If na.omit removes cases, the row numbers of the cases form the
"na.action" attribute of the result, of class "omit".
> Note the "If". If there are no NA's then nothing is done. This is
> already made explicit in Description.
I agree. Nothing is done, including passing the na.action attribute
from the original call unchanged. The help page is correct in this
sense.
This issue came to me because I found that, when writing a function to
ensure that na.action() correctly tells me whether cases are removed
from this call to na.action(), I have to remove the na.action
attribute from the input first, if it exists. It is because users of
that function may, for some reasons, use na.omit() first before
passing the object to my function. I cannot rely on the na.action
attribute for this purpose. (Maybe there are better solutions for
this. I will check.)
Again, thanks for your comments. I will use na.omit() while taking
this behavior into account.
Regards,
Shu Fai
On Mon, Mar 30, 2026 at 4:12 PM Rui Barradas <ruipbarradas using sapo.pt> wrote:
>
> Hello,
>
> Inline.
>
> Às 03:34 de 30/03/2026, Shu Fai Cheung escreveu:
> > Hi All,
> >
> > I would like to share more about the behavior of na.omit() from my
> > previous email.
> >
> > This is a test dataset:
> >
> >> DF <- data.frame(x = c(1, 2, 3, 5), y = c(NA, 10, 1, 10))
> >
> > One case omitted by na.omit()
> >> DF_omitted <- na.omit(DF)
> >> DF_omitted
> > x y
> > 2 2 10
> > 3 3 1
> > 4 5 10
> >> na.action(DF_omitted)
> > 1
> > 1
> > attr(,"class")
> > [1] "omit"
> >
> > Then I do regression on this DF_omitted (na.action can be omitted, but
> > I included it just to show that na.omit is used)
> >
> >> out1 <- lm(y ~ x,
> > + DF_omitted,
> > + na.action = na.omit)
> >> summary(out1)
> >
> > Call:
> > lm(formula = y ~ x, data = DF_omitted, na.action = na.omit)
> >
> > Residuals:
> > 2 3 4
> > 3.857 -5.786 1.929
> >
> > Coefficients:
> > Estimate Std. Error t value Pr(>|t|)
> > (Intercept) 4.8571 11.8885 0.409 0.753
> > x 0.6429 3.3404 0.192 0.879
> >
> > Residual standard error: 7.216 on 1 degrees of freedom
> > Multiple R-squared: 0.03571, Adjusted R-squared: -0.9286
> > F-statistic: 0.03704 on 1 and 1 DF, p-value: 0.879
> >
> >> na.action(out1)
> > NULL
> >> na.action(na.omit(DF_omitted))
> > 1
> > 1
> > attr(,"class")
> > [1] "omit"
> >
> > The output of lm() states that no cases were removed. To me, this is a
> > correct message because no cases were removed when fitting the model,
> > that is, in *this call* of lm(). One case was deleted, but before
> > being processed by lm(). It is not done by this call of lm().
> >
> > If the model is fitted to the original data set, then it will report,
> > again correctly, that *this call* of lm() removed one case:
> >
> >> out2 <- lm(y ~ x,
> > + DF,
> > + na.action = na.omit)
> >> summary(out2)
> >
> > Call:
> > lm(formula = y ~ x, data = DF, na.action = na.omit)
> >
> > Residuals:
> > 2 3 4
> > 3.857 -5.786 1.929
> >
> > Coefficients:
> > Estimate Std. Error t value Pr(>|t|)
> > (Intercept) 4.8571 11.8885 0.409 0.753
> > x 0.6429 3.3404 0.192 0.879
> >
> > Residual standard error: 7.216 on 1 degrees of freedom
> > (1 observation deleted due to missingness)
> > Multiple R-squared: 0.03571, Adjusted R-squared: -0.9286
> > F-statistic: 0.03704 on 1 and 1 DF, p-value: 0.879
> >
> >> na.action(out2)
> > 1
> > 1
> > attr(,"class")
> > [1] "omit"
> >
> > However, the behavior of na.omit() is different. If na.omit() is run
> > again on DF_omitted, it will say that Case 1 was removed.
> >
> >> na.action(na.omit(DF))
> > 1
> > 1
> > attr(,"class")
> > [1] "omit"
> >
> > This behavior, to me, can be misleading because no cases were removed
> > by *this call* of na.omit().
>
> Yes, they were. You are running na.omit on the original DF and na.action
> correctly reports one case omited.
>
> >
> > Moreover, from the help page of na.omit():
> >
> > 'If na.omit removes cases, the row numbers of the cases form the
> > "na.action" attribute of the result, of class "omit".'
> >
> > It does not state what happens if na.omit() does not remove cases.
> > From the example of lm() above, na.omit() will report/store that no
> > cases were removed, though this is actually the behavior of
> > model.frame()/lm(), not of na.omit()). However, when used alone,
> > na.omit() will do nothing.
> >
> > Changing this behavior of na.omit() may break a lot of things.
>
> I don't understand how it can break something. The coefs, degrees of
> freedom, etc, of the two lm calls are identically equal. Nothing is broken.
>
> If this
> > behavior of na.omit(), doing nothing, is intended (though unnatural to
> > me), perhaps this should be stated in the help page?
>
> But it does, doesn't it?
> In section Description:
>
> > na.omit returns the object with incomplete cases removed.
>
> Then, in Details:
>
> > If na.omit removes cases, the row numbers of the cases form the
> "na.action" attribute of the result, of class "omit".
>
> Note the "If". If there are no NA's then nothing is done. This is
> already made explicit in Description.
>
> NA's are always a problem, but in this case you are wrong, you are
> seeing too much in na.omit's intended behavior.
>
> Furthermore, if a function that is part of R since the beginning and
> that is used countless of times by countless users was broken, someone
> would have noticed it already. No one has because it doesn't break things.
>
> Hope this helps,
>
> Rui Barradas
>
>
> >
> > Regards,
> > Shu Fai
> >
> > On Fri, Mar 27, 2026 at 10:13 PM Shu Fai Cheung <shufai.cheung using gmail.com> wrote:
> >>
> >> Hi All,
> >>
> >> I noticed a behavior of na.omit() that I am not sure is intended.
> >>
> >> This is adapted from the example of na.omit()
> >>
> >>> DF <- data.frame(x = c(1, 2, 3), y = c(NA, 10, 1))
> >>> DF_omitted <- na.omit(DF)
> >>> attr(DF_omitted, "na.action")
> >> 1
> >> 1
> >> attr(,"class")
> >> [1] "omit"
> >>> DF_omitted2 <- na.omit(DF_omitted)
> >>> attr(DF_omitted2, "na.action")
> >> 1
> >> 1
> >> attr(,"class")
> >> [1] "omit"
> >>
> >> In the first call to na.omit(), the output, DF_omitted, correctly has only two rows, with Row 1 removed, and 'na.action' stores the removed row number.
> >>
> >> In the second call of na.omit(), no cases are removed because DF_omitted has no missing data. However, the attribute 'na.action' is retained, indicating that Row 1 was removed.
> >>
> >> To my understanding of na.omit(), this occurred because, in the second call of na.omit(), no rows were omitted, and so the original object was returned, along with the attribute 'na.action' from the first call.
> >>
> >> From the "perspective" of the second call, no rows were omitted. Should 'na.action' be NULL (i.e., not set), as in the following example?
> >>
> >>> DF2 <- data.frame(x = c(1, 2, 3), y = c(3, 10, 1))
> >>> DF2_omitted <- na.omit(DF2)
> >>> attr(DF2_omitted, "na.action")
> >> NULL
> >>
> >> Or is this behavior of na.omit() intended?
> >>
> >> Regards,
> >> Shu Fai
> >
> > ______________________________________________
> > R-help using r-project.org mailing list -- To UNSUBSCRIBE and more, see
> > https://stat.ethz.ch/mailman/listinfo/r-help
> > PLEASE do read the posting guide https://www.R-project.org/posting-guide.html
> > and provide commented, minimal, self-contained, reproducible code.
>
More information about the R-help
mailing list