Python Подсчет идентификаторов для процентных уровней

У меня есть таблица, подобная приведенной ниже, которая хранится в 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 %.

ИДЕНТИФИКАТОР А Б С 1 0% 1% 5% 3 10% 30% 50% 6 100% 300% 500% 7 700% 900% 50% 10 10% 30% 50%

Итак, результат будет что-то вроде

уровень терпимости А Б С 0% 17 100 50 1% 10 50 70 5% 60 80 40

Решение было отредактировано в процентах.

jezrael 09.05.2024 10:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
90
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Используйте 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 09.05.2024 07:34

@jezrael получил это как «ниже уровня допуска».

mozway 09.05.2024 07:37
ID falls under the nearest tolerance_level
jezrael 09.05.2024 07:37

@mozway выдает ValueError: невозможно преобразовать число с плавающей запятой NaN в целое число

testenthu 09.05.2024 07:38

@testenthu затем замените astype(int) на astype(float) или astype('int64'), это означает, что у вас есть NaN во входных данных;)

mozway 09.05.2024 07:40

@mozway, если столбец имеет процент от 200% до 300%, он принимает как 200 допуск_уровень, так и 300 допуск_уровень. Чтобы избежать этого, можем ли мы подсчитать id =o% Tolerance_level, >0 & <=1% , >1 & <=5%, >5 & <=10% и так далее....?

testenthu 09.05.2024 08:00

@testenthu нет, он не будет обрабатывать два интервала, cut присваивает только уникальный интервал. Если вы хотите изменить границу, используйте right=True, include_lowest=True. Посмотреть обновление

mozway 09.05.2024 08:15

@mozway Для столбца есть значение 17%, и оно не отображается в уровне допуска 20%.

testenthu 09.05.2024 08:22

@testenthu, можете ли вы привести точный пример в своем вопросе (с выводом, соответствующим вашему вводу)?

mozway 09.05.2024 08:27

Решение @mozway работает для меня. Я понял, что решение работает таким образом, что оно занимает 17% на уровне >=10% и <= 20%. Пожалуйста, поправьте меня, если я ошибаюсь. В этом случае другой вопрос: я хочу указать столбец допуск_уровень как строки с >=0%, >=1%, >=5%, >=10% и так....

testenthu 09.05.2024 08:42

@testenthu - Так что не нужно ближайшее совпадение - это означает, что 11 соответствует 10, 17 с 20, 12 с 10?

jezrael 09.05.2024 08:43

@jezrael да, было бы лучше, если бы оно соответствовало ближайшему.

testenthu 09.05.2024 08:49

@testenthu - ОК, добавил примеры данных в другой ответ, обработаю их для вас на реальных данных?

jezrael 09.05.2024 09:07

@jezrael выдает эту ошибку TypeError: аргумент int() должен быть строкой, байтовым объектом или числом, а не «меткой времени»

testenthu 09.05.2024 09:09

@testenthu - Понятно. Ответ отредактирован. Изменил df2['tol'] = df2['tol'].replace('%','', regex=True).astype('int64') на df2['tol'] = pd.to_numeric(df2['tol'].str.rstrip('%'), errors='coerce') и протестировал данные с помощью Timestamp.

jezrael 09.05.2024 09:14

@jezrael теперь работает с реальными данными. Но в наших результатах нет столбца Tolerance_level. Кроме того, могу ли я изменить толерантность_level в соответствии с моими потребностями?

testenthu 09.05.2024 09:24

@testenthu — Решение было отредактировано в моем ответе.

jezrael 09.05.2024 10:20

@jezrael могу ли я добавить метку % для столбца TOLERANCE_LEVEL? Типа 0%, 1%, 5%, 10%.....

testenthu 09.05.2024 10:29

Давайте продолжим обсуждение в чате.

jezrael 09.05.2024 10:59
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
"""

Другие вопросы по теме