среда, 16 ноября 2022 г.

Pandas : индексация и случайная выборка

Материал представляет некоторые методы работы с данными при использовании библиотеки Pandas. В качестве примеров используются наборы данных, связанные с розничной торговлей. 

  • Рассматриваемые методы :
    • Индексация [],loc,iloc
    • Срезы
    • Логическое индексирование
    • Методы isin(),query()
    • Случайная выборка методом sample() с весами и без
    • Метод value_counts() с нормализацией и без
    • Методы concat(),map()
    • Лямбда функции и метод apply()
    • Методы numpy : where(),select(),random.choice()


Загружаем первый набор данных
df = pd.read_excel("mydata/city_shops83.xlsx",index_col="shop")
df

cityrevvisitbalancedress_roomstore_agetype
shop
sh01city71404034546410062814mall
sh02city8832764426363584814mall
........................
sh82city949295158728543544dep_store
sh83city649087201727177163dep_store

83 rows × 7 columns

Обозначения :

  • shop - индекс магазина
  • city - индекс города
  • rev - выручка
  • visit - количество посетителей
  • balance - товарный остаток
  • dress_room - количество примерочных
  • store_age - возраст магазина в годах
  • type - тип магазина 
  • lch - длина чека = количество покупок / количество чеков
  • price - средняя цена покупки = выручка / количество покупок
  • visit_per_shift - количество посетителей на смену


Индексирование с помощью []

df1=df[['rev','visit','city']]
df1


revvisitcity
shop
sh11404034546city7
sh2832764426city8
............
sh82492951587city9
sh83490872017city6

83 rows × 3 columns


Можно переставить колонки

df1=df1[['city','visit','rev']]
df1


cityvisitrev
shop
sh1city74546140403
sh2city8442683276
............
sh82city9158749295
sh83city6201749087

83 rows × 3 columns


Доступ колонки можно получить через атрибут

df.rev


shop
sh1     140403
sh2      83276
         ...  
sh82     49295
sh83     49087
Name: rev, Length: 83, dtype: int64

Однако новую колонку добавить через атрибут не получится, надо через метку

df1['num']=list(range(len(df.index)))
df1

cityvisitrevnum
shop
sh1city714040345460
sh2city88327644261
...............
sh82city949295158781
sh83city649087201782

83 rows × 4 columns


Так можно заменить строку

df1.iloc[1] = {'city': 'city10', 'visit': 5000,'rev':100000,'num':1}
df1

cityvisitrevnum
shop
sh1city714040345460.0
sh2city1050001000001.0
...............
sh82city949295158781.0
sh83city649087201782.0

83 rows × 4 columns


Срезы по строка, первые пять строк, аналог метода head()

df[:5]

Срезы по строка, последние пять строк, аналог метода tail()

df[-5:]


cityrevvisitbalancedress_roomstore_agetype
shop
sh79city531897117219300645clothing_shop
sh80city847081174727483364clothing_shop
sh81city140197160524069844dep_store
sh82city949295158728543544dep_store
sh83city649087201727177163dep_store

Отбор по метке, метод loc

Отбор по индексу

df.loc['sh05':'sh10']


cityrevvisitbalancedress_roomstore_agetype
shop
sh5city3672044048327154814mall
sh6city5706993729303055814mall
........................
sh9city2731613404305262814dep_store
sh10city9518933315266827814dep_store

6 rows × 7 columns


Можно метки передать списком

df.loc[['sh1','sh25','sh57']]


cityrevvisitbalancedress_roomstore_agetype
shop
sh1city71404034546410062814mall
sh25city5762143486378629812dep_store
sh57city250080233425500168clothing_shop


Отбор строк по индексу и колонок по метке

df.loc[['sh1','sh25','sh57'],['city','rev','visit']]


cityrevvisit
shop
sh1city71404034546
sh25city5762143486
sh57city2500802334

Отбор по позиции, метод iloc 

Использует целочисленной индексации. Начинается с 0. При срезах включается
 начальная граница, верхняя граница не входит. Попытка использовать нецелое число,
даже допустимую метку, вызовет ошибку.

df.iloc[:3,2:]


visitbalancedress_roomstore_agetype
shop
sh014546410062814mall
sh024426363584814mall
sh034277221632814mall

df.iloc[[1,5,15],[2,4]]


visitdress_room
shop
sh0244268
sh0637298
sh1640298

Случайная выборка метод sample()

Выбираем 5 строк

df.sample(5)


cityrevvisitbalancedress_roomstore_agetype
shop
sh37city10438192296242505611clothing_shop
sh15city2341781169175895414clothing_shop
sh28city4904374323407493811mall
sh76city1058346293830336185dep_store
sh42city168557349237954489dep_store

Выбираем 10% строк

df.sample(frac=0.1)


cityrevvisitbalancedress_roomstore_agetype
shop
sh34city5640063098318369811dep_store
sh67city849550253128134066dep_store
........................
sh78city251885210127204865clothing_shop
sh53city863007315732241288dep_store

8 rows × 7 columns


По умолчанию метод возвращает каждую строку не более одного раза, 
но можно также выполнить  выборку с возвратом, задав replace=False. 
По умолчанию  каждая строка имеет одинаковую вероятность быть выбранной, но если надо, чтобы  строки имели разные вероятности быть выбранными ,  можно передавать
 веса. Эти  веса могут быть списком, массивом NumPy или серией, но они должны быть той же длины, что и объект, который вы сэмплируете. Пропущенные значения будут 
рассматриваться как нулевой вес, и значения inf не допускаются. Если сумма весов не
равна 1, они будут перенормированы путем деления всех весов на сумма весов. 


Рассмотрим наш набор данных, допустим мы хотим сдать случайную выборку, но так,
чтобы в ней были представлены все типы магазинов в равных долях. Выведем эти типы, количество магазинов и их доли, им соответствующие.


pd.DataFrame([df.type.value_counts(), df.type.value_counts(normalize=True)],
index=['count','freq'])


dep_storemallclothing_shop
count47.00020.00016.000
freq0.5660.2410.193


Как видим, долю сильно отличаются, сделаем случайную выборку из 15 строк и
посмотрим как в этой выборке распределились магазины по типу


df_smpl_1=df.sample(n=15,random_state=2)
pd.DataFrame([df_smpl_1.type.value_counts(),
df_smpl_1.type.value_counts(normalize=True)],index=['count','freq'])


dep_storemallclothing_shop
count7.04.04.0
freq0.4670.2670.267
Если мы хотим равного присутствия типов магазинов, 5 от каждого типа,
то значит вероятность выбора типа должна быть 1/3  деленная на количество
элементов в группе.

Рассчитаем веса для этого

Сначала создадим вспомогательный словарь

keys = list(df.type.value_counts().index)
values = (df.type.value_counts())
kv = list(zip(keys, values))
d = dict(kv)
d

{'dep_store': 47, 'mall': 20, 'clothing_shop': 16}

А теперь с помощью лямбда-функции и метода applay добавим в наши данные колонку весов

df['weights']=df.type.apply(lambda x: (1/3)/d.get(x))
df

cityrevvisitbalancedress_roomstore_agetypeweights
shop
sh01city71404034546410062814mall0.017
sh02city8832764426363584814mall0.017
...........................
sh82city949295158728543544dep_store0.007
sh83city649087201727177163dep_store0.007

83 rows × 8 columns


Проверим, правильно ли рассчитались веса, у всех трех типов они должны быть 1/3

df.groupby('type')['weights'].sum()


type
clothing_shop    0.333
dep_store        0.333
mall             0.333
Name: weights, dtype: float64

Все получилось верно, теперь сделаем выборку с весами и посмотрим на доли

df_smpl_2=df.sample(n=15, weights=df.weights,random_state=2)
pd.DataFrame([df_smpl_2.type.value_counts(), 
df_smpl_2.type.value_counts(normalize=True)],index=['count','freq'])

dep_storeclothing_shopmall
count6.05.0004.000
freq0.40.3330.267

Не совсем то, что хотелось, но все же ближе к цели, по крайней мере влияние
весов заметили

Рассмотрим случайные выборки из еще одного набора данных

df_day = pd.read_excel("mydata/visit_rev_day.xlsx",index_col="date")
df_day

day_on_offvisitrev
date
2019-10-0101481138
2019-10-0201591119
............
2019-10-3001621063
2019-10-3101421231

31 rows × 3 columns

Обозначения :

  • data - дата торгового дня
  • day_on_off - день недели, 0 - рабочий, 1 - выходной
  • rev - выручка
  • visit - количество посетителей

Допустим мы хотим сделать случайную выборку семи дней и рассчитать по ней средние показатели посетителей и выручки и сравнить их с истинными средними

Первая выборка

df_smpl_1=df_day.sample(n=7,random_state=2)
pd.DataFrame([df_smpl_1.day_on_off.value_counts(), 
df_smpl_1.day_on_off.value_counts(normalize=True)],index=['count','freq'])
01
count5.0002.000
freq0.7140.286
В нее попали пять рабочих и два выходных, логично.
Для второй выборки в качестве веса используем количество посетителей, помним,
что метод сам нормирует эти веса.

df_smpl_2=df_day.sample(n=7,weights=df_day.visit,random_state=2)
pd.DataFrame([df_smpl_2.day_on_off.value_counts(), 
              df_smpl_2.day_on_off.value_counts(normalize=True)],index=['count','freq'])

01
count4.0003.000
freq0.5710.429

Выборочная неделя получилось "праздничной", с тремя выходными, для расчета
среднемесячных показателей не подойдет.

В третьей выборке в качестве весов используем дневные выручки

df_smpl_3=df_day.sample(n=7,weights=df_day.rev,random_state=2)
pd.DataFrame([df_smpl_3.day_on_off.value_counts(),
              df_smpl_3.day_on_off.value_counts(normalize=True)],index=['count','freq'])

10
count4.0003.000
freq0.5710.429

Получилась еще более "праздничная" неделя четырьмя выходными.
Четвертую выборку сделаем с заранее заданным количеством рабочих и выходных,
отдельно выберем пять рабочих дней и два выходных.

df_smpl_4=pd.concat([df_day[df_day['day_on_off']==0].sample(n=5,random_state=2),
          df_day[df_day['day_on_off']==1].sample(n=2,random_state=2)])
df_smpl_4

day_on_offvisitrev
date
2019-10-2901361150
2019-10-0101481138
............
2019-10-1914303110
2019-10-0614152819

7 rows × 3 columns

Теперь посмотрим, что у нас получилось со средними выборочными показателями и сравним их с истинными средними

Отдельно сделаем набор с количеством рабочих и выходных

df_01=pd.DataFrame([df_day.day_on_off.value_counts(),
              df_smpl_1.day_on_off.value_counts(),
              df_smpl_2.day_on_off.value_counts(),
              df_smpl_3.day_on_off.value_counts(),
              df_smpl_4.day_on_off.value_counts()],     
              index=['main','samp1','samp2','samp3','samp4'])

df_01

01
main238
samp152
samp243
samp334
samp452


И набор со средними

df_02=pd.DataFrame([df_day.iloc[:,[1,2]].describe().mean(), df_smpl_1.iloc[:,[1,2]].describe().mean(), df_smpl_2.iloc[:,[1,2]].describe().mean(), df_smpl_3.iloc[:,[1,2]].describe().mean(), df_smpl_4.iloc[:,[1,2]].describe().mean(), ],index=['main','samp1','samp2','samp3','samp4']) df_02

visitrev
main185.2651336.159
samp1184.7131335.479
samp2212.1951500.340
samp3249.5841747.994
samp4187.9181367.704

И объединим их в один набор

pd.concat([df_01,df_02],axis=1)


01visitrev
main238185.2651336.159
samp152184.7131335.479
samp243212.1951500.340
samp334249.5841747.994
samp452187.9181367.704

Как видно, наиболее близкие значения получили из выборки без весов и без
предварительного разделения на рабочие и выходные

Логическое индексирование

Отбираем магазины старше 3 и моложе 10 лет

df[(df.store_age>3)&(df.store_age<10)]

cityrevvisitbalancedress_roomstore_agetype
shop
sh42city168557349237954489dep_store
sh43city584720316942357689dep_store
........................
sh81city140197160524069844dep_store
sh82city949295158728543544dep_store

41 rows × 7 columns


Убираем магазины старше 5 лет

df[~(df.store_age>5)]


cityrevvisitbalancedress_roomstore_agetype
shop
sh70city980977358833332785mall
sh71city983704345834464885dep_store
........................
sh82city949295158728543544dep_store
sh83city649087201727177163dep_store

14 rows × 7 columns


Более сложная фильтрация с использованием лямда-функции - выбираем магазины,
индекс которых заканчивается на 5-ку


criterion = df.index.map(lambda x: x.endswith('5'))
df[criterion]


cityrevvisitbalancedress_roomstore_agetype
shop
sh05city3672044048327154814mall
sh15city2341781169175895414clothing_shop
........................
sh65city1059680290726550586dep_store
sh75city857386309130558085dep_store

8 rows × 7 columns


Эквивалентный отбор, но работает медленнее

df[[x.endswith('5') for x in df.index]]


Индексирование с помощью метода isin()


Это метод позволяет вам выбирать строки, в которых один или несколько
столбцов имеют нужные вам значения: Тот же метод доступен для объектов Index и
полезен в тех случаях, когда вы не знаете, какие из меток присутствуют:

df[df.index.isin(['sh01', 'sh56', 'sh94'])]


Еще раз вернемся с к случайным выборкам. Допустим у нас есть набор данных -
дневные продажи по чекам розничного магазина
df0318 = pd.read_excel("mydata/shop_03_2018.xlsx",parse_dates=['date'])
df0318


dateyearmonthshopnumsumamt
02018-03-0120183Sh2ch20183112602
12018-03-0120183Sh2ch20183123102
........................
11142018-03-3120183Sh2ch2018331645964
11152018-03-3120183Sh2ch2018331651242

1116 rows × 7 columns


Обозначения :

  • date - дата чека
  • year - год
  • month - месяц
  • shop - индекс магазина
  • num - номер чека
  • sum - сумма чека
  • amt  -  количество вещей в чеке

И мы хотим сделать случайную выборку пяти дней и по ним рассчитать величину
среднего чека. Сразу воспользоваться методом sample нельзя, так как он делает
случайную выборку строк, а ней дней. Поэтому делаем несколько шагов.

Сначала создаем набор уникальных дат

df_date=df0318.date.unique()

И с помощью метода random.choice библиотеки numpy делаем случайную выборку без
повторов пяти дат

np.random.seed(1985) rand_date=np.random.choice(df_date, size=5, replace=False) rand_date


array(['2018-03-10T00:00:00.000000000', '2018-03-30T00:00:00.000000000',
       '2018-03-05T00:00:00.000000000', '2018-03-24T00:00:00.000000000',
       '2018-03-29T00:00:00.000000000'], dtype='datetime64[ns]')

И с помощью метода isin() отбираем соответствующими им строки с чеками

df5=df0318[df0318.date.isin(rand_date)]
df5


dateyearmonthshopnumsumamt
1752018-03-0520183Sh2ch20183515203
1762018-03-0520183Sh2ch20183525083
........................
10492018-03-3020183Sh2ch2018330232003
10502018-03-3020183Sh2ch2018330241523

196 rows × 7 columns

Рассчитываем средний чек по всему набору и по выборке

print(round(df0318.loc[:,'sum'].mean(),0),round(df5.loc[:,'sum'].mean(),0))

333.0 331.0

Значения очень близки, то есть по такой случайной выборке мы можем представить
значение среднего чека по всей совокупности

Метод query()

Метод позволяет выбирать с помощью выражения, допустимы следующие варианты :

df.query('dress_room > 4 & store_age < 5')
df.query('dress_room > 4 and store_age < 5')
df.query('type == "mall"')


Методы numpy : where и select

Заменяем строковый тип магазинов на числовой

df['type_num']=np.where(df['type']=='mall',1,np.where(df['type']=='dep_store',2,3))

cityrevvisitbalancedress_roomstore_agetypetype_num
shop
sh01city71404034546410062814mall1
sh02city8832764426363584814mall1
...........................
sh82city949295158728543544dep_store2
sh83city649087201727177163dep_store2

83 rows × 8 columns


Вариант с методом select()

conditions = [(df['type'] == 'mall'),(df['type'] == 'dep_store'),
(df['type'] == 'clothing_shop')]
choices= [1,2,3]
df['type_num_2'] = np.select(conditions, choices, default=0)


cityrevvisitbalancedress_roomstore_agetypetype_numtype_num_2
shop
sh01city71404034546410062814mall11
sh02city8832764426363584814mall11
..............................
sh82city949295158728543544dep_store22
sh83city649087201727177163dep_store22

83 rows × 9 columns








Комментариев нет:

Отправить комментарий