Pandas makes it possible to broadcast over the dimensions added via a multidimensional and even hierarchical index, and this is very powerfull, if you know how to use it. You don't need to code your loops and conditions. You can rely on what works already.
Pandas makes it possible to broadcast over the dimensions added via a multidimensional and even hierarchical index, and this is very powerfull, if you know how to use it. You don't need to code your loops and conditions. You can rely on what works already.
Broadcasting on Pandas DataFrames with MultiIndex
Broadcasting is especially interesting with DataFrames which have a pandas.MultiIndex as I show you in the following example.
I filled two pandas.DataFrames, af and df with a pandas.MultiIndex on the 0-axis (the index) and 10 columns labeled with integeres refering for example to scenario data from a Monte-Carlo simulation.
The pandas.MultiIndexes of the af and df share some common levels in the names (I call them dimensions). Not all labels (newer pandas versions call them codes) need to be in the matching dimensions. In the example, the dimensions 'a' and 'c' are shared. In both frames the 'a'-dimensions has the entries (labels) ['A' and 'B'], whereas in the 'c' dimension the frames af and bf have the entries [0, 1, 2, 3] and [0, 1, 2] respectively.
Nonetheless, Broadcasting works fine. Which means in the following example, when multiplying the two frames, a group-wise multiplication for each group with matching entries in the matching dimensions is performed.
The following example shows broadcasting on multiplications, but it works for all binary operations between pandas.DataFrames on the left- and right-hand side.
Some observations
Note, that both frames can have additional dimensions. It is not necessary that one set of names is a subset of the other. In the example we have ['a', 'b', 'c'] and ['a', 'c', 'd'] for the af and bf frames respectively
The result spans up the whole space, as expected: ['a', 'b', 'c', 'd']
Since dimension 'c' does not have the entry (code) '3' in frame bf, whereas af has, the result fills the resulting block with NaNs.
Note, that pandas 1.0.3 has been used here. Broadcasting with more then one overlapping dimensions did not work with pandas version 0.23.4.
Broadcasting over the 0-axis and the 1-axis at the same time does also work. See the last two examples. For example, if you would like to multiply the af with only bf[0].to_frame(), the first scenario. But it will only be applied to the equally labeled columns (as broadcasting is intended).
Further Hints
If you want to multiply the af frame with a column vector (I need to apply some weights sometimes with additional dimensions), then you can easily implement it yourself. You can expand your dataframe to n = af.shape[1] columns and use then that one for multiplication. Have a look at numpy.tile on how to do it 'without' coding.
>>> af Values 0 1 2 3 4 5 6 7 8 9 a b c A a 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 b 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 c 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 B a 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 b 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 c 0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 1 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 3 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 >>> bf Values 0 1 2 3 4 5 6 7 8 9 a c d A 0 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 1 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 2 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 B 0 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 1 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 2 * 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 # 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 >>> af * bf Values 0 1 2 3 4 5 6 7 8 9 a c b d A 0 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 1 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 2 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN B 0 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 1 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 2 a * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 b * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 c * 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 # 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN >>> af * bf[0] # Raises Error: ValueError: cannot join with no overlapping index names # Removed that part >>> af * bf[0].to_frame() # works consistently 0 1 2 3 4 5 6 7 8 9 a c b d A 0 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 2 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN B 0 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 2 a * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN b * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN c * 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN # 6.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN >>> cf = bf[0].to_frame() >>> cf.columns = [3] >>> af * cf # And as expected we can broadcast over the same column labels at the same time 0 1 2 3 4 5 6 7 8 9 a c b d A 0 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 1 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 2 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN B 0 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 1 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 2 a * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN b * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN c * NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN # NaN NaN NaN 6.0 NaN NaN NaN NaN NaN NaN 3 a NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN b NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN c NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN