On the default/fill value for outer joins
<p>Below are teeny/toy versions of much larger/complex dataframes I'm working with:</p>

<pre><code>&gt;&gt;&gt; A
  key         u         v         w         x
0   a  0.757954  0.258917  0.404934  0.303313
1   b  0.583382  0.504687       NaN  0.618369
2   c       NaN  0.982785  0.902166       NaN
3   d  0.898838  0.472143       NaN  0.610887
4   e  0.966606  0.865310       NaN  0.548699
5   f       NaN  0.398824  0.668153       NaN

&gt;&gt;&gt; B
  key         y         z
0   a  0.867603       NaN
1   b       NaN  0.191067
2   c  0.238616  0.803179
3   p  0.080446       NaN
4   q  0.932834       NaN
5   r  0.706561  0.814467
</code></pre>

<p>(FWIW, at the end of this post, I provide code to generate these dataframes.)</p>

<p>I want to produce an outer join of these dataframes on the <code>key</code> column<sup>1</sup>, in such a way that the new positions induced by the outer join get default value 0.0.  IOW, the desired result looks like this</p>

<pre><code>  key         u         v         w         x         y         z
0   a  0.757954  0.258917  0.404934  0.303313  0.867603       NaN
1   b  0.583382  0.504687       NaN  0.618369       NaN  0.191067
2   c       NaN  0.982785  0.902166       NaN  0.238616  0.803179
3   d  0.898838  0.472143       NaN  0.610887  0.000000  0.000000
4   e  0.966606   0.86531       NaN  0.548699  0.000000  0.000000
5   f       NaN  0.398824  0.668153       NaN  0.000000  0.000000
6   p  0.000000  0.000000  0.000000  0.000000  0.080446       NaN
7   q  0.000000  0.000000  0.000000  0.000000  0.932834       NaN
8   r  0.000000  0.000000  0.000000  0.000000  0.706561  0.814467
</code></pre>

<p>(Note that this desired output contains some NaNs, namely those that were already present in <code>A</code> or <code>B</code>.)</p>

<p>The <code>merge</code> method gets me part-way there, but the filled-in default values are NaN's, not 0.0's:</p>

<pre><code>&gt;&gt;&gt; C = pandas.DataFrame.merge(A, B, how='outer', on='key')
&gt;&gt;&gt; C
  key         u         v         w         x         y         z
0   a  0.757954  0.258917  0.404934  0.303313  0.867603       NaN
1   b  0.583382  0.504687       NaN  0.618369       NaN  0.191067
2   c       NaN  0.982785  0.902166       NaN  0.238616  0.803179
3   d  0.898838  0.472143       NaN  0.610887       NaN       NaN
4   e  0.966606  0.865310       NaN  0.548699       NaN       NaN
5   f       NaN  0.398824  0.668153       NaN       NaN       NaN
6   p       NaN       NaN       NaN       NaN  0.080446       NaN
7   q       NaN       NaN       NaN       NaN  0.932834       NaN
8   r       NaN       NaN       NaN       NaN  0.706561  0.814467
</code></pre>

<p>The <code>fillna</code> method fails to produce the desired output, because it modifies some positions that should be left unchanged:</p>

<pre><code>&gt;&gt;&gt; C.fillna(0.0)
  key         u         v         w         x         y         z
0   a  0.757954  0.258917  0.404934  0.303313  0.867603  0.000000
1   b  0.583382  0.504687  0.000000  0.618369  0.000000  0.191067
2   c  0.000000  0.982785  0.902166  0.000000  0.238616  0.803179
3   d  0.898838  0.472143  0.000000  0.610887  0.000000  0.000000
4   e  0.966606  0.865310  0.000000  0.548699  0.000000  0.000000
5   f  0.000000  0.398824  0.668153  0.000000  0.000000  0.000000
6   p  0.000000  0.000000  0.000000  0.000000  0.080446  0.000000
7   q  0.000000  0.000000  0.000000  0.000000  0.932834  0.000000
8   r  0.000000  0.000000  0.000000  0.000000  0.706561  0.814467
</code></pre>

<p>How can I achieve the desired output efficiently?  (Performance matters here, because I intend to perform this operation on much larger dataframes than those shown here.)</p>

<hr>

<p>FWIW, below is the code to generate the example dataframes <code>A</code> and <code>B</code>.</p>

<pre><code>from pandas import DataFrame
from collections import OrderedDict
from random import random, seed

def make_dataframe(rows, colnames):
    return DataFrame(OrderedDict([(n, [row[i] for row in rows])
                                 for i, n in enumerate(colnames)]))

maybe_nan = lambda: float('nan') if random() &lt; 0.4 else random()

seed(0)

A = make_dataframe([['a', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['b', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['c', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['d', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['e', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['f', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()]],
                   ('key', 'u', 'v', 'w', 'x'))

B = make_dataframe([['a', maybe_nan(), maybe_nan()],
                    ['b', maybe_nan(), maybe_nan()],
                    ['c', maybe_nan(), maybe_nan()],
                    ['p', maybe_nan(), maybe_nan()],
                    ['q', maybe_nan(), maybe_nan()],
                    ['r', maybe_nan(), maybe_nan()]],
                   ('key', 'y', 'z'))
</code></pre>

<hr>

<p><sup><sup>1</sup>For for case of <em>multi-key</em> outer joins, see <a href="http://stackoverflow.com/q/39751636/559827">here</a>.</sup></p>

<p>You can fill zeros after the <code>merge</code>:</p>

<pre><code>res = pd.merge(A, B, how="outer")
res.loc[~res.key.isin(A.key), A.columns] = 0
</code></pre>

<p><strong>EDIT</strong></p>

<p>to skip <code>key</code> column:</p>

<pre><code>res.loc[~res.key.isin(A.key), A.columns.drop("key")] = 0
</code></pre>

python pandas