title: pandas: assign to multiindex using .loc with mask
body: <p>Using the example from the <a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#using-slicers" rel="nofollow noreferrer">MultiIndex / advanced indexing: Using slicers</a> documentation.</p>
  <pre><code>def mklbl(prefix, n):
      return [&quot;%s%s&quot; % (prefix, i) for i in range(n)]
  
  
  miindex = pd.MultiIndex.from_product(
      [mklbl(&quot;A&quot;, 4), mklbl(&quot;B&quot;, 2), mklbl(&quot;C&quot;, 4), mklbl(&quot;D&quot;, 2)]
  )
  
  
  micolumns = pd.MultiIndex.from_tuples(
      [(&quot;a&quot;, &quot;foo&quot;), (&quot;a&quot;, &quot;bar&quot;), (&quot;b&quot;, &quot;foo&quot;), (&quot;b&quot;, &quot;bah&quot;)],
      names=[&quot;lvl0&quot;, &quot;lvl1&quot;]
  )
  
  
  dfmi = (
      pd.DataFrame(
          np.arange(len(miindex) * len(micolumns)).reshape(
              (len(miindex), len(micolumns))
          ),
          index=miindex,
          columns=micolumns,
      )
      .sort_index()
      .sort_index(axis=1)
  )
  </code></pre>
  <pre><code>&gt;&gt;&gt; dfmi
  lvl0           a         b
  lvl1         bar  foo  bah  foo
  A0 B0 C0 D0    1    0    3    2
           D1    5    4    7    6
        C1 D0    9    8   11   10
           D1   13   12   15   14
        C2 D0   17   16   19   18
  ...          ...  ...  ...  ...
  A3 B1 C1 D1  237  236  239  238
        C2 D0  241  240  243  242
           D1  245  244  247  246
        C3 D0  249  248  251  250
           D1  253  252  255  254
  
  [64 rows x 4 columns]
  </code></pre>
  <p>In pseudo-code, what I want:</p>
  <pre><code>if D1/bar % 3 == 0 &amp;&amp; D1/foo &gt; 100:
      D0/bar = np.nan
  </code></pre>
  <p>Almost, but not quite there:</p>
  <pre><code>mask = ( (dfmi.loc[pd.IndexSlice[:,:,:,&quot;D1&quot;], (&quot;a&quot;,&quot;bar&quot;)] % 3 == 0)
         &amp; (dfmi.loc[pd.IndexSlice[:,:,:,&quot;D1&quot;], (&quot;a&quot;,&quot;foo&quot;)] &gt; 100))
  
  dfmi.loc[pd.IndexSlice[:,:,:,&quot;D0&quot;,mask], (&quot;a&quot;,&quot;bar&quot;)] = np.nan
  </code></pre>
  <p>The issue is that at any given index level either a mask or a selector can apply - bot not both. For example, I can apply the mask at a different level. That requires the mask to be generated with a full index (no missing values) or re-aligned to the original index. How (not excluding other approaches)?</p>
  <hr />
  <p><em>Later...</em></p>
  <p>I really thought this would work as the innermost index should have half the rows, but for some reason it raises a <code>ValueError</code>. Anyone know why?</p>
  <pre><code>&gt;&gt;&gt; dfmi.swaplevel(0,3).loc[pd.IndexSlice[&quot;D0&quot;,:,:,mask.values], (&quot;a&quot;,&quot;bar&quot;)] = np.nan
  ...
  ValueError: cannot index with a boolean indexer that is not the same length as the index
  </code></pre>
  <p>While this does work, I thought there would be a cleaner way to change index values. I thought I'd used <code>index.set_levels</code> successfully in the past. Anyone care to fix this up?</p>
  <pre><code>t = mask.reset_index()
  t[&quot;level_3&quot;] = &quot;D0&quot;
  t = t.set_index(list(t.columns.values[:4]))
  mask = t.reindex(dfmi.index).fillna(False)
  dfmi.loc[mask[0], (&quot;a&quot;,&quot;bar&quot;)] = np.nan
  </code></pre>