Question: I have some time series data in a pandas dataframe. Many entries have a NaN value, and I'd like to make a matching column storing whether that entry has a NaN value or not, and then replace the NaN values with 0. While replacing the NaN values is easy enough, I can't figure out how to do the first part. If it matters, I already used a pivot operation so column names are tuples. Some of the values in the dataframe are ints and some are floats.

Example input:

```
df = pd.DataFrame(np.array([[5, 7,  np.nan], [np.nan, 8, 9.8], [7, np.nan, 12]]), columns=[('Label', 'A'), ('Label', 'B'), ('Label', 'C')])

                 Label
             A   B   C
2021-03-01   5   7 NaN
2021-03-02 NaN   8 9.8
2021-03-03   7 NaN  12

```

Desired output:

```
                                                        Label
                          A                B                C
            Has data  Value  Has data  Value  Has data  Value
2021-03-01         1      5         1      7         0      0
2021-03-02         0      0         1      8         1     98
2021-03-03         1      7         0      0         1     12

```

Answer: 
### `concat`

```
pd.concat({
    'Has data': df.notna().astype(int),
    'Value': df.fillna(0)
}).unstack(0)

              Label                                    
                  A              B              C      
           Has data Value Has data Value Has data Value
2021-03-01        1   5.0        1   7.0        0   0.0
2021-03-02        0   0.0        1   8.0        1  98.0
2021-03-03        1   7.0        0   0.0        1  12.0

```
Let me borrow the construction of the sample df from @aj7amigo (many thanks! -- but note to @quyksilver to please read [this](https://stackoverflow.com/questions/20109391/how-to-make-good-reproducible-pandas-examples))

```
df = pd.DataFrame(np.array([[5, 7,  np.nan], [np.nan, 8, 98], [7, np.nan, 12]]), columns=['A', 'B', 'C'])

```

Down below is the proposed solution. To understand it a bit better we note that this part of it

```
df.mask(~df.isna(),1).fillna(0).astype(int)

```

produces

```

    A   B   C
0   1   1   0
1   0   1   1
2   1   0   1

```

which has the 'has value' data as you want it. So now we just need to combine it with the original dataframe where we filled nans with 0s. To get it in to the shape you specified there are a few manipulations -- suggest you try these step by step to understand what they do

Here is the solution, note we do not need to loop explicitly over columns etc:

```
(pd.DataFrame({
    'Value':df.fillna(0).stack(), 
    'Has data':df.mask(~df.isna(),1)
                 .fillna(0)
                 .astype(int)
                 .stack()})
    .stack()
    .unstack(level=1)
    .unstack()
)

```

this produces

```
    A                   B                   C
    Value   Has data    Value   Has data    Value   Has data
0   5.0     1.0         7.0     1.0         0.0     0.0
1   0.0     0.0         8.0     1.0         98.0    1.0
2   7.0     1.0         0.0     0.0         12.0    1.0

```