Repository navigation
Description
Pandas version checks
-
I have checked that this issue has not already been reported.
-
I have confirmed this bug exists on the latest version of pandas.
-
I have confirmed this bug exists on the main branch of pandas.
Reproducible Example
>>> import pandas as pd
>>>
>>> x = pd.DataFrame({"a": [True, pd.NA]}, dtype="boolean")
>>> y = pd.DataFrame({"b": [pd.NA, True]}, dtype="boolean")
# If no alignment is needed, the result is expected.
>>> x | x
a
0 True
1 <NA>
>>> y | y
b
0 <NA>
1 True
# If alignment is required, the result is unexpected. The [1, a] and [0, b] entries should be pd.NA.
>>> x | y
a b
0 True True
1 True TrueIssue Description
When the logical OR operator requires the alignment (union) of indexes, the Kleene three-valued logic does not appear to be followed.
Expected Behavior
>>> x | y
a b
0 True <NA>
1 <NA> True
Installed Versions
Details
INSTALLED VERSIONS
commit : c6f4676
python : 3.14.8
python-bits : 64
OS : Linux
OS-release : 7.2.11-arch1-1
Version : #1 SMP PREEMPT_DYNAMIC Fri, 16 Oct 2026 00:28:58 +0000
machine : x86_64
processor :
byteorder : little
LC_ALL : None
LANG : en_US.UTF-8
LOCALE : en_US.UTF-8
pandas : 3.1.0
numpy : 2.5.4
dateutil : 3.0.0
pip : 26.2.1
Cython : None
sphinx : None
IPython : None
adbc-driver-postgresql: None
adbc-driver-sqlite : None
bs4 : None
bottleneck : None
fastparquet : None
fsspec : None
html5lib : None
gcsfs : None
jinja2 : None
lxml.etree : None
matplotlib : None
numba : None
numexpr : None
odfpy : None
openpyxl : None
psycopg2 : None
pymysql : None
pyarrow : None
pyiceberg : None
pyreadstat : None
pytest : None
python-calamine : None
pytz : None
pyxlsb : None
s3fs : None
scipy : None
sqlalchemy : None
tables : None
tabulate : None
xarray : None
xlrd : None
xlsxwriter : None
zstandard : None
qtpy : None
pyqt5 : None
Activity
mohamd764 commentedon Oct 22, 2026
Confirmed on pandas 3.1.0 (Python 3.13, Linux).
Same-column Kleene OR is fine (
x | x, and Series|), but when the frames need a column union the missing side is filled withnp.nan, which promotes the new column tofloat64:BooleanArray._logical_methodthen doesnp.asarray(other, dtype="bool").bool(np.nan)isTrue, so every missing cell becomesTruebeforekleene_orruns — andNA | True/False | Truecollapse toTrue. That is why AND looks fine on the same inputs:Trueis the AND identity, so the poisoned fill does not show up.A couple of nearby notes:
_should_reindex_frame_opreturnsFalsewhen the column intersection is empty, so this case goes through_align_for_op+ floatNaNfill instead of the shared-column reindex path.NaNin the non-shared boolean columns afterresult.reindex(..., axis=1)(those cells become floatNaNrather than boolean<NA>).Possible directions: keep
booleandtype on align/reindex for logical ops (fill withpd.NA), and/or treat NaNs as masked/pd.NAinBooleanArray._logical_methodinstead of casting them withdtype="bool".jorisvandenbossche commentedon Oct 22, 2026
@warwickmm thanks for the report!
@mohamd764's agent's analysis is correct. It is indeed due to the alignment introducing all-NaN columns (although I had expected them to be object dtype instead of float), and then the logical OR operation between a boolean array and an all-NaN float array behaves as this:
This is indeed because the
np.array([np.nan, np.nan])gets converted to a boolean array of [True, True]. However, the typical constructor for boolean array would actually preserve the missing values:And I think we should do that under the hood in the
BooleanArray._logical_methodas well.swethakommuri commentedon Oct 23, 2026
take
I'd like to work on this. Following @jorisvandenbossche's suggestion, I plan to convert a float/object
othercontaining NaN into a maskedBooleanArray(aspd.array(..., dtype="boolean")does) insideBooleanArray._logical_method, rather thannp.asarray(other, dtype="bool").One thing I noticed while reproducing on 3.1.0: the array-level bug isn't limited to
|.&and^are also wrong:The DataFrame
x & y/x ^ yresults happen to come out right, but the underlying array op isn't, so I'll add tests covering all three operators at both the array and DataFrame level.The partial-overlap case (
x | zleaving the non-shared column as floatNaNinstead of boolean<NA>) looks like a separate alignment/reindex concern. I'd keep it out of this PR unless you'd prefer both together.ryan1234814 commentedon Oct 23, 2026
/take
swethakommuri commentedon Oct 23, 2026
Hi @ryan1234814 - I’d claimed this just above with a plan and repro for |, & and ^ at the array level (I think the bot missed my take because the comment had more text after it). I already have the fix in BooleanArray._logical_method plus tests underway. Have you started on it? If not, would you mind if I continue? Happy to coordinate either way.
@jorisvandenbossche - I’m keeping the partial-overlap reindex case (x | z) out of scope for this one and can open it as a separate issue.
ryan1234814 commentedon Oct 25, 2026
@swethakommuri You can raise the pr in that case
ryan1234814 commentedon Oct 25, 2026
/untake
swethakommuri commentedon Oct 26, 2026
/take