У меня есть таблица, подобная приведенной ниже, которая хранится в DataFrame. Я хочу добавить уровень допуска = [0, 1, 5, 10, 20, 30, 50, 100, 200, 300, 500, 700] и получить количество идентификаторов для столбцов A, B, C, которые попадают под уровень допуска
Например: в приведенной ниже таблице подсчитано количество идентификаторов для столбца A, имеющего допуск_level 10% = 2. количество идентификаторов для столбца A, имеющего допуск_level 0% = 1 количество идентификаторов для столбцов A, имеющих допуск_уровень 100% = 1 и 700% =1
И количество идентификаторов для столбца B, имеющих допуск_level 30% = 2 и так далее..... А если в столбце есть %, который не определен в толерантном уровне, то идентификатор попадает под ближайший толерантный_уровень. Например, если столбец имеет значение 900 %, то уровень допуска будет >700 %.
Итак, результат будет что-то вроде






Используйте merge_asof для добавления нового столбца, заполненного ближайшими значениями из помощника DataFrame, и подсчета результатов с помощью перекрестной таблицы:
df = pd.DataFrame({'ID': [1, 3, 6, 7, 10],
'A': ['0%', '17%', '108%', '700%', '10%'],
'B': ['1%', '30%', '299%', '900%', '30%'],
'C': ['5%', '50%', '500%', '51%', pd.Timestamp('12-10-2000')]})
print (df)
ID A B C
0 1 0% 1% 5%
1 3 17% 30% 50%
2 6 108% 299% 500%
3 7 700% 900% 51%
4 10 10% 30% 2000-12-10 00:00:00
tolerance_level = [0, 1, 5, 10, 20, 30, 50, 100, 200, 300, 500, 700]
df1 = pd.DataFrame({'tolerance_level':tolerance_level})
df1['tolerance_level'] = df1['tolerance_level'].astype('float')
df2 = df.melt('ID', value_name='tol')
df2['tol'] = pd.to_numeric(df2['tol'].str.rstrip('%'), errors='coerce')
df3 = pd.merge_asof(df2.dropna(subset=['tol']).sort_values('tol'),
df1,
left_on='tol',
right_on='tolerance_level',
direction='nearest')
out = (pd.crosstab(df3['tolerance_level'], df3['variable'])
.rename_axis(columns=None)
.rename(lambda x: f"{x}%")
.reset_index())
print (out)
tolerance_level A B C
0 0% 1 0 0
1 1% 0 1 0
2 5% 0 0 1
3 10% 1 0 0
4 20% 1 0 0
5 30% 0 2 0
6 50% 0 0 3
7 100% 1 0 0
8 300% 0 1 0
9 500% 0 0 1
10 700% 1 1 0
Измените форму ввода с помощью расплавить , сформируйте ячейки из tolerance_level с помощью pandas.cut (после удаления % с помощью str.rstrip ), затем вычислите перекрестную таблицу:
tolerance_level = [0, 1, 5, 10, 20, 30, 50, 100, 200, 300, 500, 700]
tmp = df.melt('ID', value_name='tolerance_level')
out = pd.crosstab(pd.cut(tmp['tolerance_level'].str.rstrip('%').astype(float),
bins=tolerance_level+[np.inf], labels=tolerance_level,
right=False),
tmp['variable']
).reset_index()
Выход:
tolerance_level A B C
0 0 1 0 0
1 1 0 1 0
2 5 0 0 1
3 10 2 0 0
4 30 0 2 0
5 50 0 0 3
6 100 1 0 0
7 300 0 1 0
8 500 0 0 1
9 700 1 1 0
Вышеупомянутое присваивает заданное значение ячейке с ближайшей нижней границей. Если вы хотите назначить ближайшую верхнюю границу, измените:
out = pd.crosstab(pd.cut(tmp['tolerance_level'].str.rstrip('%').astype(float),
bins=tolerance_level+[np.inf], labels=tolerance_level,
right=True, include_lowest=True),
tmp['variable']
).rename_axis(columns=None).reset_index()
Выход:
tolerance_level A B C
0 0 1 1 0
1 1 0 0 1
2 5 2 0 0
3 20 0 2 0
4 30 0 0 3
5 50 1 0 0
6 200 0 1 0
7 300 0 0 1
8 500 1 0 0
9 700 0 1 0
Да, единственное решение, не работающее с ближайшим уровнем допуска, например, с необходимостью OP.
@jezrael получил это как «ниже уровня допуска».
ID falls under the nearest tolerance_level@mozway выдает ValueError: невозможно преобразовать число с плавающей запятой NaN в целое число
@testenthu затем замените astype(int) на astype(float) или astype('int64'), это означает, что у вас есть NaN во входных данных;)
@mozway, если столбец имеет процент от 200% до 300%, он принимает как 200 допуск_уровень, так и 300 допуск_уровень. Чтобы избежать этого, можем ли мы подсчитать id =o% Tolerance_level, >0 & <=1% , >1 & <=5%, >5 & <=10% и так далее....?
@testenthu нет, он не будет обрабатывать два интервала, cut присваивает только уникальный интервал. Если вы хотите изменить границу, используйте right=True, include_lowest=True. Посмотреть обновление
@mozway Для столбца есть значение 17%, и оно не отображается в уровне допуска 20%.
@testenthu, можете ли вы привести точный пример в своем вопросе (с выводом, соответствующим вашему вводу)?
Решение @mozway работает для меня. Я понял, что решение работает таким образом, что оно занимает 17% на уровне >=10% и <= 20%. Пожалуйста, поправьте меня, если я ошибаюсь. В этом случае другой вопрос: я хочу указать столбец допуск_уровень как строки с >=0%, >=1%, >=5%, >=10% и так....
@testenthu - Так что не нужно ближайшее совпадение - это означает, что 11 соответствует 10, 17 с 20, 12 с 10?
@jezrael да, было бы лучше, если бы оно соответствовало ближайшему.
@testenthu - ОК, добавил примеры данных в другой ответ, обработаю их для вас на реальных данных?
@jezrael выдает эту ошибку TypeError: аргумент int() должен быть строкой, байтовым объектом или числом, а не «меткой времени»
@testenthu - Понятно. Ответ отредактирован. Изменил df2['tol'] = df2['tol'].replace('%','', regex=True).astype('int64') на df2['tol'] = pd.to_numeric(df2['tol'].str.rstrip('%'), errors='coerce') и протестировал данные с помощью Timestamp.
@jezrael теперь работает с реальными данными. Но в наших результатах нет столбца Tolerance_level. Кроме того, могу ли я изменить толерантность_level в соответствии с моими потребностями?
@testenthu — Решение было отредактировано в моем ответе.
@jezrael могу ли я добавить метку % для столбца TOLERANCE_LEVEL? Типа 0%, 1%, 5%, 10%.....
Давайте продолжим обсуждение в чате.
import pandas as pd
import numpy as np
data = [
{'ID': 1, 'A': '0%', 'B': '1%', 'C': '5%'},
{'ID': 3, 'A': '10%', 'B': '30%', 'C': '50%'},
{'ID': 6, 'A': '100%', 'B': '300%', 'C': '500%'},
{'ID': 7, 'A': '700%', 'B': '900%', 'C': '50%'},
{'ID': 10, 'A': '10%', 'B': '30%', 'C': '50%'}
]
df = pd.DataFrame(data)
print(df)
"""
ID A B C
0 1 0% 1% 5%
1 3 10% 30% 50%
2 6 100% 300% 500%
3 7 700% 900% 50%
4 10 10% 30% 50%
"""
tolerance_levels = np.array([0, 1, 5, 10, 20, 30, 50, 100, 200, 300, 500, 700])
df_numeric = df[['A', 'B', 'C']].apply(lambda x: x.str.rstrip('%').astype(float))
# half tolerance for each level
half_tolerance = np.where(tolerance_levels > 0, tolerance_levels / 2, 0)
# Initialize a DataFrame to store the counts
counts_df = pd.DataFrame(index=tolerance_levels, columns=df_numeric.columns, dtype=int).fillna(0)
counts_df.index.name = 'tolerance_levels' # Set the name of the index
print(counts_df)
"""
A B C
tolerance_levels
0 0.0 0.0 0.0
1 0.0 0.0 0.0
5 0.0 0.0 0.0
10 0.0 0.0 0.0
20 0.0 0.0 0.0
30 0.0 0.0 0.0
50 0.0 0.0 0.0
100 0.0 0.0 0.0
200 0.0 0.0 0.0
300 0.0 0.0 0.0
500 0.0 0.0 0.0
700 0.0 0.0 0.0
"""
for tl, ht in zip(tolerance_levels, half_tolerance):
cond1 = (df_numeric >= (tl - ht) )
cond2 = (df_numeric <= (tl + ht) )
cond = (cond1 & cond2)
counts_df.loc[tl] = cond.sum()
counts_df.index = counts_df.index.astype(str) + '%'
print(counts_df)
"""
A B C
tolerance_levels
0% 1.0 0.0 0.0
1% 0.0 1.0 0.0
5% 0.0 0.0 1.0
10% 2.0 0.0 1.0
20% 2.0 2.0 0.0
30% 0.0 2.0 0.0
50% 0.0 2.0 3.0
100% 1.0 0.0 3.0
200% 1.0 1.0 0.0
300% 0.0 1.0 0.0
500% 1.0 1.0 1.0
700% 1.0 1.0 1.0
"""
Решение было отредактировано в процентах.