Я пытаюсь найти правильный синтаксис, чтобы выбрать срез строк в Pandas DataFrame, обусловленный многомерным срезом.
Я хочу выполнить бинонирование гистограмм, предоставив буферы в многомерном массиве numpy и сравнивая в любом случае, подходит ли запись в одном бункере или другом. Результат должен быть 1-мерным массивом numpy с количеством элементов в каждом бункере.
Моя первоначальная попытка модели ниже, для справки, хотя с тех пор я предоставил частичную реализацию (используя цикл вместо этого) в ответе ниже:
import numpy as np
import pandas as pd
## Generate Random Data
X = np.random.normal(0.5,0.1,100)
## Populate a Pandas DataFrame
DF = pd.DataFrame({'x':X})
## Some example, hardcoded 1D bins.
bins = np.array([
[[0.0,0.2]],
[[0.2,0.4]],
[[0.4,0.6]],
[[0.6,0.8]],
[[0.8,1.0]]
])
hist = np.zeros(shape=(4,))
hist[:] = np.sum(
DF.loc[ (DF >= bins[:,:,0]) &
(DF > bins[:,:,1])
].dropna(how='all')
)
В общем случае данные являются n-мерными, а бункеры следуют приведенному выше шаблону, причем:
[[x_min, x_max], [y_min, ymax], [z_min, z_max]]
для каждого бункера (следовательно, кажущийся "дополнительный" слой вложенности в примере 1D выше). Поэтому нарезка должна работать для DataFrames из нескольких столбцов, так что
DF['x'] >= x_min and DF['x'] < x_max and
DF['y'] >= y_min and DF['y'] < y_max
и т.д. и, следовательно, должны быть агрегированными по размерности; подход нарезки, по-видимому, является наиболее естественным способом достижения этого и должен быть более эффективным с точки зрения вычислительной эффективности, если это будет достижимо.
Если нет, в моем ответе можно было бы подходить к пониманию списка, но у меня были проблемы с многомерностью.
Как упоминалось в моем комментарии к ответу SpghttCd, я нашел рабочий подход, который использует заполнение списка вместо среза при заполнении гистограммы. Похоже, что он точно подсчитывает количество записей в каждом ящике (тестируется в 1D и 2D), но является неэлегантным, и я был бы благодарен за улучшения для людей, более близко знакомых с библиотекой панд. Похоже, что это немного хитрое из-за целочисленного округления.
Я представляю приведенный ниже код, пример выше расширенный до 2D.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as patches
## Generate Random Data
X = np.random.normal(0.5,0.1,150)
Y = np.random.normal(0.5,0.2,150)
## Populate a Pandas DataFrame
DF = pd.DataFrame({'x':X,'y':Y})
## Some example, hardcoded 2D bins.
bins = np.array([
[[0.0,0.2],[0.0,1.5]],
[[0.2,0.4],[0.0,1.5]],
[[0.4,0.6],[0.0,1.5]],
[[0.6,0.8],[0.0,1.5]],
[[0.8,1.0],[0.0,1.5]]
])
hist = np.array([
np.product(
np.sum( (DF.iloc[:,:] >= bins[:,:,0][i][:]) &
(DF.iloc[:,:] < bins[:,:,1][i][:])
))/len(DF)
for i in range(len(bins)) ], dtype=np.int32)[:,0]
print(hist)
print(sum(hist))
## 2D Plot
plt.style.use('seaborn')
fig, axes = plt.subplots(figsize=(4, 3.5))
plt.scatter(DF['x'],DF['y'], 5, 'k')
axes.set_xlabel('x')
axes.set_xlabel('y')
axes.set_xlim(-0.5,1.5)
axes.set_ylim(-0.5,2)
# Create a Rectangle patch for each bin and plot
for i,bin in enumerate(bins):
rect = patches.Rectangle( (bin[0][0],bin[1][0]),
bin[0][1]-bin[0][0],
bin[1][1]-bin[1][0],
linewidth=1,
edgecolor='r',facecolor='none')
# Add the patch to the Axes
axes.add_patch(rect)
plt.show()
Это часть личного проекта для повторного создания N-мерных гистограмм в Python со ссылкой на обсуждение в этом вопросе SciComp.
Я не уверен, действительно ли вам нужны панды, но numpy имеет многомерную функцию histogramdd
называемую histogramdd
.
Вот тестовый цикл, который генерирует три массива с увеличением количества столбцов, всего 100 строк и соответствующих массивов бункеров, все с вашими границами образцов сверху.
Просто посмотрите, если это то, что вы искали:
for i in range(1, 4):
data = np.random.random([100, i])
bins = np.linspace(0, 1, 6)
bins = [bins for _ in range(i)]
print('shape of data: ', np.shape(data))
print('bin borders: ',bins)
print('\nresult: ', np.histogramdd(data, bins), '\n\n')
результат:
shape of data: (100, 1)
bin borders: [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])]
result: (array([ 14., 26., 21., 24., 15.]), [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])])
shape of data: (100, 2)
bin borders: [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])]
result: (array([[ 5., 7., 5., 2., 3.],
[ 5., 4., 5., 3., 1.],
[ 5., 3., 7., 1., 3.],
[ 2., 6., 4., 3., 7.],
[ 1., 11., 3., 2., 2.]]), [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])])
shape of data: (100, 3)
bin borders: [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])]
result: (array([[[ 1., 0., 0., 0., 2.],
[ 0., 1., 1., 1., 0.],
[ 0., 1., 1., 2., 1.],
[ 2., 2., 0., 2., 0.],
[ 1., 1., 1., 2., 1.]],
[[ 2., 0., 1., 1., 1.],
[ 0., 0., 0., 1., 0.],
[ 1., 2., 2., 0., 1.],
[ 0., 1., 1., 2., 0.],
[ 0., 0., 1., 1., 0.]],
[[ 1., 0., 0., 0., 1.],
[ 1., 0., 2., 0., 4.],
[ 0., 1., 0., 1., 1.],
[ 2., 0., 0., 0., 0.],
[ 1., 1., 0., 1., 0.]],
[[ 1., 2., 1., 1., 0.],
[ 0., 1., 1., 0., 2.],
[ 2., 1., 1., 0., 1.],
[ 2., 0., 1., 1., 0.],
[ 0., 2., 0., 2., 1.]],
[[ 1., 3., 0., 1., 0.],
[ 1., 1., 0., 0., 0.],
[ 1., 1., 0., 0., 0.],
[ 1., 1., 2., 1., 1.],
[ 1., 1., 1., 0., 1.]]]), [array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ]), array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])])
bins[:][0]
иbins[:][1]
все еще являются массивами. Тем не менее, убедитесь, что вы выполняете код или добавляете код с ошибками, только прямо объяснив, в чем ваша проблема.hist[:] =
) так, чтобы я правильно заполнил этот объект. Буду признателен за предложения о том, как правильно написать этот фрагмент, или, если это невозможно, объяснение, почему или как сделать это по-другому.