reshaping a DataFrame with non-unique index - python

I have the following DataFrame:
In [299]: df
Out[299]:
a b
DATE
2017-05-28 15:01:37 0.0 1.0
2017-05-28 15:01:39 1.0 0.0
2017-05-28 15:01:39 1.0 0.0
2017-05-28 15:01:39 1.0 0.0
2017-05-28 15:01:39 1.0 0.0
2017-05-28 15:01:39 1.0 0.0
2017-05-28 15:01:42 1.0 0.0
2017-05-28 15:02:10 1.0 0.0
2017-05-28 15:02:14 0.0 1.0
2017-05-28 15:02:23 0.0 1.0
2017-05-28 15:02:28 1.0 0.0
2017-05-28 15:02:34 0.0 1.0
2017-05-28 15:02:34 0.0 1.0
I can get the shape I'm looking for by doing the following:
In [300]: xa = df.groupby(df.index).apply(lambda x: x['a'].values)
In [301]: xb = df.groupby(df.index).apply(lambda x: x['b'].values)
In [302]: ya = pd.DataFrame(xa.tolist(), index=xa.index)
In [303]: yb = pd.DataFrame(xb.tolist(), index=xb.index)
In [304]: new_df = pd.concat([ya, yb], axis=1, keys=['a', 'b'])
In [305]: new_df
Out[305]:
a b
0 1 2 3 4 0 1 2 3 4
DATE
2017-05-28 15:01:37 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:01:39 1.0 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0
2017-05-28 15:01:42 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:10 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:14 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:02:23 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:02:28 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:34 0.0 0.0 NaN NaN NaN 1.0 1.0 NaN NaN NaN
Is there a more efficient way to get the same result?

Append an index level with cumcount
df.set_index(df.groupby(level='DATE').cumcount(), append=True).unstack()
a b
0 1 2 3 4 0 1 2 3 4
DATE
2017-05-28 15:01:37 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:01:39 1.0 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0
2017-05-28 15:01:42 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:10 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:14 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:02:23 0.0 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
2017-05-28 15:02:28 1.0 NaN NaN NaN NaN 0.0 NaN NaN NaN NaN
2017-05-28 15:02:34 0.0 0.0 NaN NaN NaN 1.0 1.0 NaN NaN NaN

Related

Python: Forward fill values of a multiindex DataFrame on a daily, monthly and quarterly basis based on the value of first level index

I have a multiindex DataFrame with level=0 being the date and level=1 being the stock index, and the columns being the constituent tickers of each stock index. The values are tallies recording when the ticker is in the index (1.0) and when it's not (NaN). The DataFrame is as follows:
df:
ticker TSLA AAPL MSFT GLD FB BRK JPM
Date symbol
2022-01-01 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
2022-01-02 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
2022-01-03 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
2022-01-04 DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
...
2022-01-30 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
2022-02-01 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
...
2022-02-28 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
2022-03-01 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
What I want is to forward-fill the tally (1.0) for the tickers based on the previous tally for the symbol on the following basis:
SPY -> Daily tally
NASDAQ -> Monthly tally
DOW -> Quarterly tally
and then I just want to take the values of the symbol at these frequencies (i.e. daily values for SPY, the monthly value for NASDAQ, the quarterly value for DOW).
So the final DataFrame should look like this:
df_final:
ticker TSLA AAPL MSFT GLD FB BRK JPM
Date symbol
2022-01-01 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
2022-01-02 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
2022-01-03 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
...
2022-01-30 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
2022-02-01 SPY 1.0 1.0 NaN NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
...
2022-03-01 SPY 1.0 1.0 1.0 NaN NaN 1.0 1.0
NASDAQ 1.0 1.0 1.0 NaN NaN NaN NaN
DOW NaN NaN 1.0 1.0 NaN 1.0 1.0
How can this be achieved using pandas? (i.e. I want to minimize the amount of loops)

Transforming pandas dataframe, where column entries are column headers

My dataset has 12 columns, X1-X6 and Y1-Y6. The variables X and Y match to each other - the first record means: 80 parts of A, 10 parts of C, 2 parts of J and 8 parts of K (each row has 100 total).
I would like to be able to transform my dataset into a dataset in which the entries in columns X1-X6 are now the headers. See before and after datasets below.
My dataset (before):
X1 X2 X3 X4 X5 X6 Y1 Y2 Y3 Y4 Y5 Y6
0 A C J K NaN NaN 80.0 10.0 2.0 8.0 NaN NaN
1 F N O NaN NaN NaN 2.0 25.0 73.0 NaN NaN NaN
2 A H J M NaN NaN 70.0 6.0 15.0 9.0 NaN NaN
3 B I K P NaN NaN 0.5 1.5 2.0 96.0 NaN NaN
4 A B F H O P 83.0 4.0 9.0 2.0 1.0 1.0
5 A B F G NaN NaN 1.0 16.0 9.0 74.0 NaN NaN
6 A B D F L NaN 95.0 2.0 1.0 1.0 1.0 NaN
7 B F H P NaN NaN 0.2 0.4 0.4 99.0 NaN NaN
8 A D F L NaN NaN 35.0 12.0 30.0 23.0 NaN NaN
9 A B F I O NaN 95.0 0.3 0.1 1.6 3.0 NaN
10 B E G NaN NaN NaN 10.0 31.0 59.0 NaN NaN NaN
11 A F G L NaN NaN 24.0 6.0 67.0 3.0 NaN NaN
12 A C I NaN NaN NaN 65.0 30.0 5.0 NaN NaN NaN
13 A F G L NaN NaN 55.0 6.0 4.0 35.0 NaN NaN
14 A F J K L NaN 22.0 3.0 12.0 0.8 62.2 NaN
15 B F I P NaN NaN 0.6 1.2 0.2 98.0 NaN NaN
16 A B F H O NaN 27.0 6.0 46.0 13.0 8.0 NaN
The dataset I'd like to transform to:
A B C D E F G H I J K L M \
0 80.0 NaN 10.0 NaN NaN NaN NaN NaN NaN 2.0 8.0 NaN NaN
1 NaN NaN NaN NaN NaN 2.0 NaN NaN NaN NaN NaN NaN NaN
2 70.0 NaN NaN NaN NaN NaN NaN 6.0 NaN 15.0 NaN NaN 9.0
3 NaN 0.5 NaN NaN NaN NaN NaN NaN 1.5 NaN 2.0 NaN NaN
4 83.0 4.0 NaN NaN NaN 9.0 NaN 2.0 NaN NaN NaN NaN NaN
5 1.0 16.0 NaN NaN NaN 9.0 74.0 NaN NaN NaN NaN NaN NaN
6 95.0 2.0 NaN 1.0 NaN 1.0 NaN NaN NaN NaN NaN 1.0 NaN
7 NaN 0.2 NaN NaN NaN 0.4 NaN 0.4 NaN NaN NaN NaN NaN
8 35.0 NaN NaN 12.0 NaN 30.0 NaN NaN NaN NaN NaN 23.0 NaN
9 95.0 0.3 NaN NaN NaN 0.1 NaN NaN 1.6 NaN NaN NaN NaN
10 NaN 10.0 NaN NaN 31.0 NaN 59.0 NaN NaN NaN NaN NaN NaN
11 24.0 NaN NaN NaN NaN 6.0 67.0 NaN NaN NaN NaN 3.0 NaN
12 65.0 NaN 30.0 NaN NaN NaN NaN NaN 5.0 NaN NaN NaN NaN
13 55.0 NaN NaN NaN NaN 6.0 4.0 NaN NaN NaN NaN 35.0 NaN
14 22.0 NaN NaN NaN NaN 3.0 NaN NaN NaN 12.0 0.8 62.2 NaN
15 NaN 0.6 NaN NaN NaN 1.2 NaN NaN 0.2 NaN NaN NaN NaN
16 27.0 6.0 NaN NaN NaN 46.0 NaN 13.0 NaN NaN NaN NaN NaN
N O P
0 NaN NaN NaN
1 25.0 73.0 NaN
2 NaN NaN NaN
3 NaN NaN 96.0
4 NaN 1.0 1.0
5 NaN NaN NaN
6 NaN NaN NaN
7 NaN NaN 99.0
8 NaN NaN NaN
9 NaN 3.0 NaN
10 NaN NaN NaN
11 NaN NaN NaN
12 NaN NaN NaN
13 NaN NaN NaN
14 NaN NaN NaN
15 NaN NaN 98.0
16 NaN 8.0 NaN
As you know that you want the Xi part to contain the column names for the new dataframe, while the Yi part would be the value, it is enough to change every line in a dict where Xi is the key and Yi the value. Then you use the list of that dictionnaries to feed the new dataframe:
data = list(df.apply(lambda x: {x['X'+ str(i)]: x['Y'+str(i)] for i in range(1,7)
if x['X'+str(i)]!= 'NaN'}, axis=1))
resul = pd.DataFrame(data)
print(resul)
gives:
A B C D E F ... K L M N O P
0 80.0 NaN 10.0 NaN NaN NaN ... 8.0 NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN 2.0 ... NaN NaN NaN 25.0 73.0 NaN
2 70.0 NaN NaN NaN NaN NaN ... NaN NaN 9.0 NaN NaN NaN
3 NaN 0.5 NaN NaN NaN NaN ... 2.0 NaN NaN NaN NaN 96.0
4 83.0 4.0 NaN NaN NaN 9.0 ... NaN NaN NaN NaN 1.0 1.0
5 1.0 16.0 NaN NaN NaN 9.0 ... NaN NaN NaN NaN NaN NaN
6 95.0 2.0 NaN 1.0 NaN 1.0 ... NaN 1.0 NaN NaN NaN NaN
7 NaN 0.2 NaN NaN NaN 0.4 ... NaN NaN NaN NaN NaN 99.0
8 35.0 NaN NaN 12.0 NaN 30.0 ... NaN 23.0 NaN NaN NaN NaN
9 95.0 0.3 NaN NaN NaN 0.1 ... NaN NaN NaN NaN 3.0 NaN
10 NaN 10.0 NaN NaN 31.0 NaN ... NaN NaN NaN NaN NaN NaN
11 24.0 NaN NaN NaN NaN 6.0 ... NaN 3.0 NaN NaN NaN NaN
12 65.0 NaN 30.0 NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
13 55.0 NaN NaN NaN NaN 6.0 ... NaN 35.0 NaN NaN NaN NaN
14 22.0 NaN NaN NaN NaN 3.0 ... 0.8 62.2 NaN NaN NaN NaN
15 NaN 0.6 NaN NaN NaN 1.2 ... NaN NaN NaN NaN NaN 98.0
16 27.0 6.0 NaN NaN NaN 46.0 ... NaN NaN NaN NaN 8.0 NaN
[17 rows x 16 columns]
One way to handle this. Loop through each row, splitting the dataframe in half using iloc. Then build a new dictionary using zip, then create a resulting dataframe.
df_dict = {x: list(zip(df.iloc[x,0:6], df.iloc[x,6:12])) for x in range(df.shape[0])}
df1 = pd.DataFrame.from_dict(pd_dict, orient='index')
df1.sort_index(1)
A B C F H I J K M N O P nan
0 80.0 NaN 10.0 NaN NaN NaN 2.0 8.0 NaN NaN NaN NaN NaN
1 NaN NaN NaN 2.0 NaN NaN NaN NaN NaN 25.0 73.0 NaN NaN
2 70.0 NaN NaN NaN 6.0 NaN 15.0 NaN 9.0 NaN NaN NaN NaN
3 NaN 0.5 NaN NaN NaN 1.5 NaN 2.0 NaN NaN NaN 96. NaN
4 83.0 4.0 NaN 9.0 2.0 NaN NaN NaN NaN NaN 1.0 1.0 NaN

Treat the missing values (conditional Imputation ) Pandas

I have a block in the processing of missing data
i have this df
index a b c del
2018-06-25 12:51:00 NaN NaN NaN 1
2018-06-25 12:52:00 NaN NaN NaN NaN
2018-06-25 12:53:00 NaN NaN NaN NaN
2018-06-25 12:54:00 NaN NaN NaN NaN
2018-06-25 12:55:00 NaN NaN NaN NaN
2018-06-25 12:56:00 NaN NaN NaN NaN
2018-06-25 12:57:00 NaN NaN NaN NaN
2018-06-25 12:58:00 0.5 0.6 0.6 0.0
2018-06-25 12:59:00 NaN NaN NaN 0.0
2018-06-25 13:00:00 0.6 0.8 0.6 0.0
2018-06-25 13:01:00 NaN NaN NaN 0.0
2018-06-25 13:02:00 0.6 0.9 0.6 0.0
what I want to do
interpolate missing values if the value of del is not missing
i have this solution keep just the df when del has a value and I do my interpolation after I concatene
but I want to avoid concatenation and too much line of code and variables
dfs= df.loc[df['del'].notnull(),:]
dfs.interpolate(method='time')
dfs1= df.loc[df['del'].isnull(),:]
dfs= pd.concat([dfs,dfs1], axis=1, sort=True, join_axes=[df.index])
desired output :
index a b c del
2018-06-25 12:51:00 NaN NaN NaN 1.0
2018-06-25 12:52:00 NaN NaN NaN NaN
2018-06-25 12:53:00 NaN NaN NaN NaN
2018-06-25 12:54:00 NaN NaN NaN NaN
2018-06-25 12:55:00 NaN NaN NaN NaN
2018-06-25 12:56:00 NaN NaN NaN NaN
2018-06-25 12:57:00 NaN NaN NaN NaN
2018-06-25 12:58:00 0.5 0.6 0.6 0.0
2018-06-25 12:59:00 0.6 0.7 0.7 0.0
2018-06-25 13:00:00 0.6 0.8 0.6 0.0
2018-06-25 13:01:00 0.7 0.9 0.7 0.0
2018-06-25 13:02:00 0.6 0.9 0.6 0.0
story of just closing the little hole by column
Thank you <<
Use .loc to assign the interpolation only where 'del' is not null:
df.loc[df['del'].notnull()] = df.loc[df['del'].notnull()].interpolate(method='time')
a b c del
2018-06-25 12:51:00 NaN NaN NaN 1.0
2018-06-25 12:52:00 NaN NaN NaN NaN
2018-06-25 12:53:00 NaN NaN NaN NaN
2018-06-25 12:54:00 NaN NaN NaN NaN
2018-06-25 12:55:00 NaN NaN NaN NaN
2018-06-25 12:56:00 NaN NaN NaN NaN
2018-06-25 12:57:00 NaN NaN NaN NaN
2018-06-25 12:58:00 0.50 0.60 0.6 0.0
2018-06-25 12:59:00 0.55 0.70 0.6 0.0
2018-06-25 13:00:00 0.60 0.80 0.6 0.0
2018-06-25 13:01:00 0.60 0.85 0.6 0.0
2018-06-25 13:02:00 0.60 0.90 0.6 0.0

Having trouble sorting by row index and `sort_index()` isn't working

I'm attempting to sort the row indexes below from largest to smallest:
My first attempt was:
plot_df_dropoff.sort_index(by=["dropoff_latitude"], ascending=False)
But I get the a Key Value Error.
Second thought based on this link didn't work either. It returned None.
This seems so simple but I can't figure it out. Any help would be much appreciated.
id
pickup_longitude (-74.03, -74.025] (-74.025, -74.02] (-74.02, -74.015] (-74.015, -74.01] (-74.01, -74.005] (-74.005, -74] (-74, -73.995] (-73.995, -73.99] (-73.99, -73.985] (-73.985, -73.98] ... (-73.82, -73.815] (-73.815, -73.81] (-73.81, -73.805] (-73.805, -73.8] (-73.8, -73.795] (-73.795, -73.79] (-73.79, -73.785] (-73.785, -73.78] (-73.78, -73.775] (-73.775, -73.77]
pickup_latitude
(40.63, 40.64] 5.0 10.0 8.0 2.0 3.0 1.0 NaN 2.0 1.0 1.0 ... NaN NaN NaN NaN 1.0 NaN 7.0 1.0 NaN NaN
(40.64, 40.65] 2.0 2.0 14.0 16.0 2.0 4.0 6.0 3.0 5.0 11.0 ... NaN NaN NaN 149.0 164.0 3580.0 7532.0 11381.0 5596.0 NaN
(40.65, 40.66] NaN NaN NaN 2.0 22.0 41.0 11.0 2.0 4.0 13.0 ... NaN 1.0 146.0 7.0 3.0 201.0 81.0 2.0 1.0 2.0
(40.66, 40.67] NaN NaN NaN NaN NaN 2.0 60.0 143.0 180.0 122.0 ... NaN 4.0 24.0 126.0 15.0 47.0 32.0 4.0 3.0 3.0
(40.67, 40.68] NaN NaN 7.0 44.0 18.0 200.0 328.0 65.0 293.0 590.0 ... 3.0 3.0 1.0 131.0 1.0 1.0 2.0 1.0 1.0 2.0
And here is a smaller segment that might be easier to work with:
id \
pickup_longitude (-74.03, -74.025] (-74.025, -74.02] (-74.02, -74.015]
pickup_latitude
(40.63, 40.64] 5.0 10.0 8.0
(40.64, 40.65] 2.0 2.0 14.0
(40.65, 40.66] NaN NaN NaN
(40.66, 40.67] NaN NaN NaN
(40.67, 40.68] NaN NaN 7.0
(40.68, 40.69] NaN NaN NaN
(40.69, 40.7] NaN 1.0 1.0
(40.7, 40.71] 1.0 1.0 3841.0
(40.71, 40.72] NaN 2.0 6537.0
(40.72, 40.73] NaN NaN NaN
(40.73, 40.74] 9.0 2.0 NaN
You can reset index and sort by values.
Try:
>>>plot_df_dropoff.reset_index().sort_values(by=["dropoff_latitude"], ascending=False)
And as #JohnE mentioned, you can also just use sort_index():
>>>plot_df_dropoff.sort_index(ascending=False)

What does the term "broadcasting" mean in Pandas documentation?

I'm reading through the Pandas documentation, and the term "broadcasting" is used extensively, but never really defined or explained.
What does it mean?
So the term broadcasting comes from numpy, simply put it explains the rules of the output that will result when you perform operations between n-dimensional arrays (could be panels, dataframes, series) or scalar values.
Broadcasting using a scalar value
So the simplest case is just multiplying by a scalar value:
In [4]:
s = pd.Series(np.arange(5))
s
Out[4]:
0 0
1 1
2 2
3 3
4 4
dtype: int32
In [5]:
s * 10
Out[5]:
0 0
1 10
2 20
3 30
4 40
dtype: int32
and we get the same expected results with a dataframe:
In [6]:
df = pd.DataFrame({'a':np.random.randn(4), 'b':np.random.randn(4)})
df
Out[6]:
a b
0 0.216920 0.652193
1 0.968969 0.033369
2 0.637784 0.856836
3 -2.303556 0.426238
In [7]:
df * 10
Out[7]:
a b
0 2.169204 6.521925
1 9.689690 0.333695
2 6.377839 8.568362
3 -23.035557 4.262381
So what is technically happening here is that the scalar value has been broadcasted along the same dimensions of the Series and DataFrame above.
Broadcasting using a 1-D array
Say we have a 2-D dataframe of shape 4 x 3 (4 rows x 3 columns) we can perform an operation along the x-axis by using a 1-D Series that is the same length as the row-length:
In [8]:
df = pd.DataFrame({'a':np.random.randn(4), 'b':np.random.randn(4), 'c':np.random.randn(4)})
df
Out[8]:
a b c
0 0.122073 -1.178127 -1.531254
1 0.011346 -0.747583 -1.967079
2 -0.019716 -0.235676 1.419547
3 0.215847 1.112350 0.659432
In [26]:
df.iloc[0]
Out[26]:
a 0.122073
b -1.178127
c -1.531254
Name: 0, dtype: float64
In [27]:
df + df.iloc[0]
Out[27]:
a b c
0 0.244146 -2.356254 -3.062507
1 0.133419 -1.925710 -3.498333
2 0.102357 -1.413803 -0.111707
3 0.337920 -0.065777 -0.871822
the above looks funny at first until you understand what is happening, I took the first row of values and added this row-wise to the df, it can be visualised using this pic (sourced from scipy):
The general rule is this:
In order to broadcast, the size of the trailing axes for both arrays
in an operation must either be the same size or one of them must be
one.
So if I tried to add a 1-D array that didn't match in length, say one with 4 elements, unlike numpy which will raise a ValueError, in Pandas you'll get a df full of NaN values:
In [30]:
df + pd.Series(np.arange(4))
Out[30]:
a b c 0 1 2 3
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN
Now some of the great things about pandas is that it will try to align using existing column names and row labels, this can get in the way of trying to perform a fancier broadcasting like this:
In [55]:
df[['a']] + df.iloc[0]
Out[55]:
a b c
0 0.244146 NaN NaN
1 0.133419 NaN NaN
2 0.102357 NaN NaN
3 0.337920 NaN NaN
In the above I use double subscripting to force the shape to be (4,1) but we see a problem when trying to broadcast using the first row as the column alignment only aligns on the first column. To get the same form of broadcasting to occur like the diagram above shows we have to decompose to numpy arrays which then become anonymous data:
In [56]:
df[['a']].values + df.iloc[0].values
Out[56]:
array([[ 0.24414608, -1.05605392, -1.4091805 ],
[ 0.13341899, -1.166781 , -1.51990758],
[ 0.10235701, -1.19784299, -1.55096957],
[ 0.33792013, -0.96227987, -1.31540645]])
It's also possible to broadcast in 3-dimensions but I don't go near that stuff often but the numpy, scipy and pandas book have examples that show how that works.
Generally speaking the thing to remember is that aside from scalar values which are simple, for n-D arrays the minor/trailing axes length must match or one of them must be 1.
Update
it seems that the above now leads to ValueError: Unable to coerce to Series, length must be 1: given 3 in latest version of pandas 0.20.2
so you have to call .values on the df first:
In[42]:
df[['a']].values + df.iloc[0].values
Out[42]:
array([[ 0.244146, -1.056054, -1.409181],
[ 0.133419, -1.166781, -1.519908],
[ 0.102357, -1.197843, -1.55097 ],
[ 0.33792 , -0.96228 , -1.315407]])
To restore this back to the original df we can construct a df from the np array and pass the original columns in the args to the constructor:
In[43]:
pd.DataFrame(df[['a']].values + df.iloc[0].values, columns=df.columns)
Out[43]:
a b c
0 0.244146 -1.056054 -1.409181
1 0.133419 -1.166781 -1.519908
2 0.102357 -1.197843 -1.550970
3 0.337920 -0.962280 -1.315407
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.
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.
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

Categories