シフト作成メモ

作成途中
メモ用
プログラムがちがち初心者

```python:qiita.rb

import pandas as pd
import numpy as np
from random import random
import time, os
import openpyxl as opx
pd.options.display.precision = 0


def read_excel():
 
 df = pd.read_excel(r"C:\Users\keisu\OneDrive\デスクトップ\月次シフト.xlsx", index_col = 0, header=[1,])
 df = df.fillna("0")
 df = df.replace("0", 0).replace("◎", 3)

 kiso = df.iloc[2:, 0:31].reset_index(drop = True)
 holiday = df.iloc[2:,31:32].reset_index(drop = True)
 holiday.columns = ["休日数"]
 kouritu = df.iloc[2:,32:33].reset_index(drop = True)
 kouritu.columns = ["効率"]
 people = df.iloc[0:2, 0:31].reset_index(drop = True)
 people.index = ["日勤", "夜勤"]
 
 return kiso, holiday, kouritu, people


def first_gene(kiso, holiday):
    days = len(kiso.columns)
    kiso_copy = kiso.copy()
    
    for k in range(len(kiso_copy)):
     h = []
     while len(h) < holiday.loc[k][0]:
         n = np.random.randint(1, days + 1)
         if not n in h:
             h.append(n)
     #h以外のリスト=出勤数 filter(n = n !== h)　　
     not_h =  [i for i in range(1, days + 1) if i not in h]
     for i in not_h:
         #日勤が1、夜勤が2
         x = 1 if 0.8 > random() else 2
         if kiso_copy.loc[k,i] == 0:
            kiso_copy.loc[k,i] = x

    return kiso_copy
  
  
def holiday_fix(kiso, holiday):
    
    #人ごとに見る、kiso_copyで出勤をすべて1にして、判定用に使う、希望休を消さないため
    kiso_copy = kiso.copy()
    kiso_copy.replace(2,1)
    for k in range(len(kiso)):
     days = len(kiso.columns)
     if np.count_nonzero(kiso_copy.iloc[k:k + 1] == 0) + np.count_nonzero(kiso_copy.iloc[k:k + 1] == 3) != holiday.iloc[k][0]:
         s = np.count_nonzero(kiso_copy.iloc[k:k + 1] == 0) + np.count_nonzero(kiso_copy.iloc[k:k + 1] == 3) - holiday.iloc[k][0]
         buf = 0
         c1 = 0 if s > 0 else 1
         c2 = 1 if c1 == 0 else 0
         #増減したい休日数に達するまでループ　夜勤数は固定するため、休み⇔日勤のみ
         while buf < abs(s):
            n = np.random.randint(1, days)
            if kiso.loc[k][n] == c1:
                    buf += 1
                    #休日数を操作
                    kiso.loc[k,n] = c2
    return kiso


def yakin_fix(kiso, holiday):
    
    #日ごとにみる、kiso_copyで出勤をすべて1にして、判定用に使う、希望休を消さないため
    kiso_copy = kiso.copy()
    for k in range(len(kiso.columns)):
     if np.count_nonzero(kiso_copy.iloc[:,k:k + 1] == 2) != people.iloc[1,k]:
         s = np.count_nonzero(kiso_copy.iloc[:,k:k + 1] == 2) - people.iloc[1,k]
         buf = 0
         c1 = 2 if s > 0 else 1
         c2 = 1 if c1 == 2 else 2
         #増減したい夜勤数に達するまでループ　休日数は固定するため、夜勤⇔日勤のみ
         while buf < abs(s):
            n = np.random.randint(0, len(kiso))
            if kiso.iloc[n,k] == c1:
                    buf += 1
                    #夜勤数を操作
                    kiso.iloc[n,k] = c2
    return kiso


def evaluation_function(kiso):

 # 評価
 eva1 = kiso.copy()
 eva1.replace(2, 1)
 eva2 = kiso.copy()
 score = 0

 # メンバーごとに評価
 for k in range(len(eva1)):
     # 結合
     x = ''.join([str(i) for i in np.array(eva1.iloc[k:k + 1]).flatten()])
     # 5連勤以上
     score += np.sum([((2 - len(i))**2)*-1 for i in x.split("0") if len(i) > 5])
     # 3連休以上
     score += np.sum([((1 - len(i))**2)*-1 for i in x.split("1") if len(i) > 3])
     # 飛び石連休
     score += -10*(len(x.split("010"))-1)
 
 # 日勤必要数
 score += np.sum([abs(people.iloc[0,k] - np.sum(eva2.iloc[:,k] == 1)) * -4 for k in range(len(eva2.columns))])
 
 # 効率
 K = []
 Kouritu = []
 for i in range(len(eva1.columns)):
    for j in range(len(eva1)):
        if eva1.iloc[j,i] == 1:
          K.append(j)
    if len(K) != 0:
     Kouritu.append(round(np.mean([kouritu.iloc[k,0] for k in K])))

    
 score += np.var(Kouritu)* -4

    
 return score


def crossover(ep, sd, p1,p2):
 
    days = len(p1.columns)
    p1 = np.array(p1).flatten()
    p2 = np.array(p2).flatten()
    
    ch1 = []
    ch2 = []
    
    for p1_, p2_ in zip(p1,p2):
      x = True if ep > random() else False
      if x == True:
        ch1.append(p1_)
        ch2.append(p2_)
      else:
        ch1.append(p2_)
        ch2.append(p1_)
    
    ch1, ch2 = mutation(sd, np.array(ch1).flatten(), np.array(ch2).flatten())
    
    ch1 = pd.DataFrame(ch1.reshape(int(len(ch1)/days), days))
    ch2 = pd.DataFrame(ch2.reshape(int(len(ch2)/days), days))
    ch1.columns = [i+1 for i in range(len(ch1.columns))]
    ch2.columns = [i+1 for i in range(len(ch2.columns))]
    
    return ch1, ch2


def mutation(sd, ch1, ch2):
  
  #夜勤は変えない
  x = True if sd > random() else False
  if x == True:
        rand = np.random.permutation([i for i in range(len(ch1))])
        rand = rand[:int(len(ch1)//10)]
        for i in rand:
         if ch1[i] == 1:
            ch1[i] = 0
         
         elif ch1[i] == 0:
            ch1[i] = 1
            
  x = True if sd > random() else False
  if x == True:
        rand = np.random.permutation([i for i in range(len(ch1))])
        rand = rand[:int(len(ch2)//10)]
        for i in rand:
         if ch2[i] == 1:
            ch2[i] = 0
         
         elif ch2[i] == 0:
            ch2[i] = 1
  return ch1, ch2

first_length = 100
elite_length = 20
gene_length = 10
ep = 0.5
sd = 0.1

kiso, holiday, kouritu, people = read_excel()

parent = []

for i in range(first_length):
  kiso = first_gene(kiso, holiday)
  kiso = holiday_fix(kiso, holiday)
  kiso = yakin_fix(kiso, holiday)
  score = evaluation_function(kiso)
  parent.append([score,kiso])

for i in range(gene_length):
  korenp = np.array(parent, dtype=object)
  parent = sorted(korenp,key=lambda x: -x[0])
  parent = parent[:elite_length]

  if i == 0 or top[0] < parent[0][0]:
    top = parent[0]
  else:
    parent.append(top)

  print(f"第{i+1}世代")
  print(top[0])

  children = []
  for k1,v1 in enumerate(parent):
   for k2,v2 in enumerate(parent):
     if k1 < k2:
       ch1,ch2 = crossover(ep,sd, v1[1], v2[1])
       ch1 = yakin_fix(ch1, holiday)
       ch2 = yakin_fix(ch2, holiday)
       ch1 = holiday_fix(ch1, holiday)
       ch2 = holiday_fix(ch2, holiday)   
       score1 = evaluation_function(ch1)
       score2 = evaluation_function(ch2)
       children.append([score1,ch1])
       children.append([score2,ch2])
  
  parent = children.copy()
    
x = top[1].replace(1, "日").replace(2, "夜").replace(0, "●").replace(3, "●")
x

```