Repository: pandas-dev/pandas

Issue:
BUG: Adding DataFrames with misaligned MultiIndex produces NaN despite fill_value=0
### Pandas version checks

- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the [latest version](https://pandas.pydata.org/docs/whatsnew/index.html) of pandas.

- [x] I have confirmed this bug exists on the [main branch](https://pandas.pydata.org/docs/dev/getting_started/install.html#installing-the-development-version-of-pandas) of pandas.


### Reproducible Example

```python
import pandas as pd

# Create two DataFrames with MultiIndex
index1 = pd.MultiIndex.from_tuples([('A', 'one'), ('A', 'two')])
index2 = pd.MultiIndex.from_tuples([('B', 'one'), ('B', 'two')])

df1 = pd.DataFrame([[1, 2]], columns=index1)
df2 = pd.DataFrame([[3, 4]], columns=index2)

# Adding DataFrames with different MultiIndex
result = df1.add(df2, fill_value=0)
print(result)
```

### Issue Description

I have two data frames with unaligned multi-indices. When adding the two data frames, with fill_value=0, I'd expect the missing values to be replaced with zero before performing the addition operation, as described in the documentation of `DataFrame.add`. However, the above example produces this output:

```
    A       B    
  one two one two
0 NaN NaN NaN NaN
```

The problem affects the current main branch (tested with commit https://github.com/pandas-dev/pandas/commit/e557039fda7d4325184cf76520892b3a635ec2dd). No released version is affected (yet). The behavior was introduced by https://github.com/pandas-dev/pandas/pull/60538. Before this PR, the code was producing the expected output.

### Expected Behavior

I'd expect this output:

```
A B
one two one two
0 1.0 2.0 3.0 4.0
```


### Installed Versions

<details>
INSTALLED VERSIONS
------------------
commit                : 72fd708761f1598f1a8ce9b693529b81fd8ca252
python                : 3.10.8
python-bits           : 64
OS                    : Linux
OS-release            : 5.15.0-130-generic
Version               : #140-Ubuntu SMP Wed Dec 18 17:59:53 UTC 2024
machine               : x86_64
processor             : 
byteorder             : little
LC_ALL                : None
LANG                  : C.UTF-8
LOCALE                : en_US.UTF-8

pandas                : 3.0.0.dev0+1839.g72fd708761
numpy                 : 1.26.4
dateutil              : 2.9.0.post0
pip                   : 24.2
Cython                : 3.0.11
sphinx                : 8.1.3
IPython               : 8.30.0
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : 4.12.3
blosc                 : None
bottleneck            : 1.4.2
fastparquet           : 2024.11.0
fsspec                : 2024.10.0
html5lib              : 1.1
hypothesis            : 6.122.4
gcsfs                 : 2024.10.0
jinja2                : 3.1.4
lxml.etree            : 5.3.0
matplotlib            : 3.10.0
numba                 : 0.60.0
numexpr               : 2.10.2
odfpy                 : None
openpyxl              : 3.1.5
psycopg2              : 2.9.10
pymysql               : 1.4.6
pyarrow               : 18.1.0
pyreadstat            : 1.2.8
pytest                : 8.3.4
python-calamine       : None
pytz                  : 2024.2
pyxlsb                : 1.0.10
s3fs                  : 2024.10.0
scipy                 : 1.14.1
sqlalchemy            : 2.0.36
tables                : 3.10.1
tabulate              : 0.9.0
xarray                : 2024.9.0
xlrd                  : 2.0.1
xlsxwriter            : 3.2.0
zstandard             : 0.23.0
tzdata                : 2024.2
qtpy                  : None
pyqt5                 : None
</details>


Discussion:

Patch:
diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx
index 5239aa2c61..fce51700d6 100644
--- a/pandas/_libs/lib.pyx
+++ b/pandas/_libs/lib.pyx
@@ -1522,6 +1522,11 @@ def infer_dtype(value: object, skipna: bool = True) -> str:
     """
     Return a string label of the type of a scalar or list-like of values.
 
+    This method inspects the elements of the provided input and determines
+    classification of its data type. It is particularly useful for
+    handling heterogeneous data inputs where explicit dtype conversion may not
+    be possible or necessary.
+
     Parameters
     ----------
     value : scalar, list, ndarray, or pandas type
diff --git a/pandas/core/arrays/sparse/accessor.py b/pandas/core/arrays/sparse/accessor.py
index 0ed5f69fe4..eab8527eef 100644
--- a/pandas/core/arrays/sparse/accessor.py
+++ b/pandas/core/arrays/sparse/accessor.py
@@ -279,6 +279,11 @@ class SparseFrameAccessor(BaseAccessor, PandasDelegate):
     """
     DataFrame accessor for sparse data.
 
+    It allows users to interact with a `DataFrame` that contains sparse data types
+    (`SparseDtype`). It provides methods and attributes to efficiently work with sparse
+    storage, reducing memory usage while maintaining compatibility with standard pandas
+    operations.
+
     Parameters
     ----------
     data : scipy.sparse.spmatrix
diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py
index d8dd644191..6be6787862 100644
--- a/pandas/core/dtypes/dtypes.py
+++ b/pandas/core/dtypes/dtypes.py
@@ -161,6 +161,11 @@ class CategoricalDtype(PandasExtensionDtype, ExtensionDtype):
     """
     Type for categorical data with the categories and orderedness.
 
+    It is a dtype representation for categorical data, which allows users to define
+    a fixed set of values and optionally impose an ordering. This is particularly
+    useful for handling categorical variables efficiently, as it can significantly
+    reduce memory usage compared to using object dtypes.
+
     Parameters
     ----------
     categories : sequence, optional
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index 57a7b9467a..3199733cfb 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -8029,10 +8029,15 @@ class DataFrame(NDFrame, OpsMixin):
             return False
 
         if (
-            isinstance(self.columns, MultiIndex)
-            or isinstance(right.columns, MultiIndex)
-        ) and not self.columns.equals(right.columns):
+            (
+                isinstance(self.columns, MultiIndex)
+                or isinstance(right.columns, MultiIndex)
+            )
+            and not self.columns.equals(right.columns)
+            and fill_value is None
+        ):
             # GH#60498 Reindex if MultiIndexe columns are not matching
+            # GH#60903 Don't reindex if fill_value is provided
             return True
 
         if fill_value is None and level is None and axis == 1:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index f376518d4d..874ab1a3c9 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -406,6 +406,12 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         """
         Return a new object with updated flags.
 
+        This method creates a shallow copy of the original object, preserving its
+        underlying data while modifying its global flags. In particular, it allows
+        you to update properties such as whether duplicate labels are permitted. This
+        behavior is especially useful in method chains, where one wishes to
+        adjust DataFrame or Series characteristics without altering the original object.
+
         Parameters
         ----------
         copy : bool, default False
diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py
index f2ec41d2c6..c9bea58751 100644
--- a/pandas/io/formats/style.py
+++ b/pandas/io/formats/style.py
@@ -117,6 +117,12 @@ class Styler(StylerRenderer):
     r"""
     Helps style a DataFrame or Series according to the data with HTML and CSS.
 
+    This class provides methods for styling and formatting a Pandas DataFrame or Series.
+    The styled output can be rendered as HTML or LaTeX, and it supports CSS-based
+    styling, allowing users to control colors, font styles, and other visual aspects of
+    tabular data. It is particularly useful for presenting DataFrame objects in a
+    Jupyter Notebook environment or when exporting styled tables for reports and
+
     Parameters
     ----------
     data : Series or DataFrame
diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py
index 703a2b3656..e032e26d77 100644
--- a/pandas/io/json/_json.py
+++ b/pandas/io/json/_json.py
@@ -520,6 +520,12 @@ def read_json(
     """
     Convert a JSON string to pandas object.
 
+    This method reads JSON files or JSON-like data and converts them into pandas
+    objects. It supports a variety of input formats, including line-delimited JSON,
+    compressed files, and various data representations (table, records, index-based,
+    etc.). When `chunksize` is specified, an iterator is returned instead of loading
+    the entire data into memory.
+
     Parameters
     ----------
     path_or_buf : a str path, path object or file-like object
diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py
index abad825a9a..5cedb41fdc 100644
--- a/pandas/io/pytables.py
+++ b/pandas/io/pytables.py
@@ -1131,6 +1131,12 @@ class HDFStore:
         """
         Store object in HDFStore.
 
+        This method writes a pandas DataFrame or Series into an HDF5 file using
+        either the fixed or table format. The `table` format allows additional
+        operations like incremental appends and queries but may have performance
+        trade-offs. The `fixed` format provides faster read/write operations but
+        does not support appends or queries.
+
         Parameters
         ----------
         key : str


Test patch:
diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py
index aa2d5e9d23..8239de3f39 100644
--- a/pandas/tests/frame/test_arithmetic.py
+++ b/pandas/tests/frame/test_arithmetic.py
@@ -2058,6 +2058,26 @@ def test_arithmetic_multiindex_column_align():
     tm.assert_frame_equal(result, expected)
 
 
+def test_arithmetic_multiindex_column_align_with_fillvalue():
+    # GH#60903
+    df1 = DataFrame(
+        data=[[1.0, 2.0]],
+        columns=MultiIndex.from_tuples([("A", "one"), ("A", "two")]),
+    )
+    df2 = DataFrame(
+        data=[[3.0, 4.0]],
+        columns=MultiIndex.from_tuples([("B", "one"), ("B", "two")]),
+    )
+    expected = DataFrame(
+        data=[[1.0, 2.0, 3.0, 4.0]],
+        columns=MultiIndex.from_tuples(
+            [("A", "one"), ("A", "two"), ("B", "one"), ("B", "two")]
+        ),
+    )
+    result = df1.add(df2, fill_value=0)
+    tm.assert_frame_equal(result, expected)
+
+
 def test_bool_frame_mult_float():
     # GH 18549
     df = DataFrame(True, list("ab"), list("cd"))