# シフト最適化システム OptShift


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

![](10shift_files/figure-commonmark/cell-2-output-1.jpeg)

\#hide \### シフト型の最適化モデル（時間帯を考慮しないモデル）

集合：

- *I* : スタッフの集合 $= {1,2, , i, |I|} $
- *J*<sub>*i*</sub> : スタッフ *i* が就くことができるシフトの集合
- *T* : 計画日の集合 $= {1,2, , t, |T|} $

パラメータ：

- *c*<sub>*i**j**t*</sub> : 日 *t* にスタッフ *i* がシフト *j*
  に就くときの費用（希望休日出勤の場合には大きくする）．
- *L**B*<sub>*j**t*</sub> : 日*t* のシフト *j* で必要なスタッフの人数
- *p*<sub>*j**t*</sub> : 日 *t* のシフト *j*
  のスタッフ不足時のペナルティ費用
- *B*<sub>*i*</sub> : スタッフ *i* の勤務日数

変数：

- *x*<sub>*i**j**t*</sub> : スタッフ　*i*　が日 *t* にシフト *j*
  に就くとき 1、それ以外のとき 0 を表す0-1変数

SCOPで解く場合は， 変数 *X*<sub>*i**t*</sub> を用い，その領域を
*J*<sub>*i*</sub> とする．

- *y*<sub>*i**t*</sub>: スタッフ　*i*　が日 *t* に休むとき
  1、それ以外のとき 0 を表す0-1変数

- *ξ*<sub>*j**t*</sub> : 日　*t*, シフト *j*
  のスタッフ不足数を表す整数変数

定式化：

- 目的関数（スタッフの費用と必要人数逸脱ペナルティの和）

*m**i**n**i**m**i**z**e*  ∑*c*<sub>*i**j**t*</sub>*x*<sub>*i**j**t*</sub> + ∑*p*<sub>*j**t*</sub>*ξ*<sub>*j**t*</sub>

- スタッフがいずれかのシフトに就くか，休むことを表す制約

∑*x*<sub>*i**j**t*</sub> + *y*<sub>*i**t*</sub> = 1   ∀*i*, *t*

- 連続勤務最大日数　連続勤務は５日まで（6日間の間には必ず１つ休みが入る）

$$
\sum\_{s=t}^{t+5} y\_{is} \geq 1 \\\\\\\forall i,t 
$$

- 連休回避　４日連続の休みは回避（連続する４日の中の休みの合計が3以下；ただし希望休は除く）

$$
\sum\_{s=t}^{t+3} y\_{is} \leq 3 \\\\\\\forall i,t 
$$

- 必要人数下限制約

∑<sub>*i* : *j* ∈ *J*<sub>*i*</sub></sub>*x*<sub>*i**j**t*</sub> + *ξ*<sub>*j**t*</sub> ≥ *L**B*<sub>*j**t*</sub>   ∀*j*, *t*

- 勤務日数制約

∑*y*<sub>*i**t*</sub> = *B*<sub>*i*</sub>   ∀*i*

- スタッフ1が休みの時はスタッフ2は出勤とする制約

*y*<sub>1*t*</sub> + *y*<sub>2*t*</sub> ≤ 1   ∀*t*

- 連続勤務最大日数；
  「できるだけ」連続勤務は4日まで（5日間の間には必ず１つ休みが入る）

（破った場合の超過変数を追加し，目的関数にペナルティを入れる；以下同様）

$$
\sum\_{s=t}^{t+4} y\_{is} \geq 1 \\\\\\\forall i,t 
$$

- 休，出勤，休みのパターンをできるだけ回避

*y*<sub>*i**t*</sub> − *y*<sub>*i*, *t* + 1</sub> + *y*<sub>*i*, *t* + 2</sub> ≤ 1   ∀*i*, *t*

- 遅番・早番の連続はできるだけ回避

遅番のシフトの集合を LATE，早番のシフトの集合を EARLYとする．

∑<sub>*j* ∈ *L**A**T**E*</sub>*x*<sub>*i**j**t*</sub> + ∑<sub>*j* ∈ *E**A**R**L**Y*</sub>*x*<sub>*i**j*, *t* + 1</sub> ≤ 1   ∀*i*, *t*

``` python
#| include: false
#model.write("shift.lp")
model.optimize()
eps = 0.001
shift = np.zeros((n_staff,n_day)) #0が休みと仮定
violation = np.zeros((n_job,n_day))
for (i,j,t) in x:
    if x[i,j,t].X >= 1.- eps:
    #    print(i,j,t,x[i,j,t].X)
        shift[i,t] = j
print("下限逸脱[j,t]")
for (j,t) in lb_violation:
    if lb_violation[j,t].X > eps:
        print(j,t,lb_violation[j,t].X)
        violation[j,t] = lb_violation[j,t].X
print("逸脱ペナルティ")
for (k,i,t) in surplus:
    if surplus[k,i,t].X > eps:
        if k==0:
            print("連続勤務最大日数 スタッフ",i,t,"日")
        elif k==1:
            print("休，出勤，休み スタッフ",i,t,"日")
        else:
            print("遅番・早番の連続 スタッフ",i,t,"日")
        #print(k,i,t,surplus[k,i,t].X)
```

    Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (mac64[x86])
    Thread count: 8 physical cores, 16 logical processors, using up to 16 threads
    Optimize a model with 4096 rows, 6293 columns and 21824 nonzeros
    Model fingerprint: 0xa3de674e
    Variable types: 0 continuous, 6293 integer (3782 binary)
    Coefficient statistics:
      Matrix range     [1e+00, 1e+00]
      Objective range  [1e+00, 1e+04]
      Bounds range     [1e+00, 1e+00]
      RHS range        [1e+00, 3e+01]
    Found heuristic solution: objective 93808.000000
    Presolve removed 731 rows and 1083 columns
    Presolve time: 0.02s
    Presolved: 3365 rows, 5210 columns, 19435 nonzeros
    Found heuristic solution: objective 91456.000000
    Variable types: 0 continuous, 5210 integer (4784 binary)

    Root relaxation: objective 4.820000e+02, 3118 iterations, 0.09 seconds (0.08 work units)

        Nodes    |    Current Node    |     Objective Bounds      |     Work
     Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

    *    0     0               0     482.0000000  482.00000  0.00%     -    0s

    Explored 1 nodes (4054 simplex iterations) in 0.17 seconds (0.15 work units)
    Thread count was 16 (of 16 available processors)

    Solution count 3: 482 91456 93808 

    Optimal solution found (tolerance 1.00e-04)
    Best objective 4.820000000000e+02, best bound 4.820000000000e+02, gap 0.0000%
    下限逸脱[j,t]
    逸脱ペナルティ
    連続勤務最大日数 スタッフ 9 2 日
    連続勤務最大日数 スタッフ 9 9 日
    連続勤務最大日数 スタッフ 9 16 日
    連続勤務最大日数 スタッフ 9 23 日
    CPU times: user 478 ms, sys: 17.7 ms, total: 496 ms
    Wall time: 190 ms

\#hide

### Excel出力

Openpyxlを用いて，結果をExcelファイルに出力する．

## シフト最適化

実際問題から生じた以下の仮定の問題を高速に解くための関数群

問題は以下の仮定をもつ．

-　100人程度のスタッフ - 30日程度の計画日 -
30分もしくは1時間単位の計画期（時間帯） -
スタッフは作業が可能な業務が与えられている（スキルの設定） -
休憩を入れるためのルール - スタッフのシフト希望 -
日・時間帯・作業ごとの必要人数下限

制約条件を満たす解のうちスタッフの費用の合計を最小化

## モデルの定式化

\#hide

集合・パラメータ・変数を以下に示す.

集合：

- *I* : スタッフの集合 $= {1,2, , i, |I|} $

- *J* : スタッフが就く業務の集合 $= {1,2, , j, |J|} $
  ；この中には休憩を表す *r* が含まれるものとする。

- *D* : 計画日の集合 $= {1,2, , d, |D|} $

- *T*<sub>*d*</sub> :
  計画日*d*における計画期（時間帯；例えば１時間や30分を1単位とする）の集合
  $= {0,1, , t, |T\_{d}|-1} $

- *T*′<sub>*i**d*</sub> : スタッフ*i*の日*d*における勤務可能期の集合
   = {*t* ∣ *o*<sub>*i**d*</sub> ≤ *t* ≤ *e*<sub>*i**d*</sub>, *t* ∈ *T*<sub>*d*</sub>}
  （*o*<sub>*i**d*</sub>と*e*<sub>*i**d*</sub>
  はそれぞれ開始可能期と終了期）

- *O*<sub>*i**d*</sub> : 日 *d*
  にスタッフ*i*が、{*s* ∣ *o*<sub>*i**d*</sub> ≤ *s* \< *e*<sub>*i**d*</sub> − *m* + 1, *s* ∈ *T*<sub>*d*</sub>}
  期に勤務を開始し,
  {*t* ∣ *s* + *m* − 1 ≤ *t* ≤ *e*<sub>*i**d*</sub>, *t* ∈ *T*<sub>*d*</sub>}期に勤務を終了するという組み合わせ(*s*, *t*)を要素とした集合
  （*m* は最低勤務期間数）

- *D*<sub>*w*</sub>: 週 *w* に含まれる日の集合

- *W* : 週の集合

パラメータ：

- *c*<sub>*i**s**t*</sub> :
  スタッフ*i*が*s*期に勤務を開始し,*t*期に勤務を終了するときの費用
- *w*<sub>*i**s**t*</sub> :
  スタッフ*i*が*s*期に勤務を開始し,*t*期に勤務を終了するときの（休憩時間を除いた）稼働期数
- *L**B*<sub>*d**t**j*</sub> :
  日*d*の期*t*に業務*j*で必要なスタッフの人数
- *h*<sub>*d**t**j*</sub> :
  日*d*の期*t*における業務*j*のスタッフ不足時のペナルティ費用
- *m* :
  最低勤務期間数.勤務開始から勤務終了まで*m*期以上勤務しなければならないものとする。
- *B*<sub>*i*</sub> : スタッフ*i*の計画期間中の勤務日数上限
- *o*<sub>*i**d*</sub> : スタッフ*i*の日*d*における勤務開始可能期
- *e*<sub>*i**d*</sub> : スタッフ*i*の日*d*における勤務終了期限期
- *τ*<sub>*s**t*</sub> : *s*期から*t*期まで勤務したときに必要な休憩期数
- *β* : 休憩を開始直後から（もしくは終了直前に）とることを禁止した期数
- *ζ*<sub>*i*</sub> :
  スタッフ*i*が勤務業務を変更したときのペナルティ費用

変数：

- *x*<sub>*i**d**s**t*</sub> : スタッフ*i*が日*d*期*s*に勤務を開始し,期
  *t* に勤務を終了するとき 1、それ以外のとき 0 を表す0-1変数

- *y*<sub>*i**d**t**j*</sub>: スタッフ*i*が日*d*期*t*に業務*j*に就くとき
  1、それ以外のとき 0 を表す0-1変数

- *ξ*<sub>*d**t**j*</sub> : 日*d*, 期*t*,
  業務*j*のスタッフ不足数を表す整数変数

- *u*<sub>*i**d**t**j*</sub> :
  スタッフ*i*が日*d*期*t*に勤務業務を休憩以外の別の勤務業務から*j*に変更したとき
  1、それ以外のとき 0 を表す0-1変数

定式化：

$$
\begin{array}{lll}
 minimize & \sum\_{ i \in I} \sum\_{ d \in D} \sum\_{(s,t) \in O\_{id}}c\_{ist} x\_{idst} 
+ \sum\_{ d \in D} \sum\_{ t \in T\_{d}} \sum\_{j \in J} h\_{dtj} \xi\_{dtj} 
+ \sum\_{i \in I} \sum\_{d \in D} \sum\_{t \in T\_{d}} \sum\_{j \in J\_{i}} \zeta\_{i} u\_{idtj} &  \\
s.t. & \sum\_{j \in J\_{i}} y\_{idkj} = \sum\_{(s,t):s \leq k \leq t , (s,t) \in O\_{id}} x\_{idst} & \forall i \in I, d \in D, k \in T'\_{id} \\
 & \sum\_{(s,t) \in O\_{id}} x\_{idst} \leq 1  & \forall i \in I, d \in D  \\
 & \sum\_{k \in T'\_{id}} y\_{idkr} = \sum\_{(s,t) \in O\_{id}} \tau\_{st} x\_{idst} &  \forall i \in I, d \in D \\
 & \sum\_{i|j \in J\_{i}} y\_{idtj} + \xi\_{dtj} \geq LB\_{dtj} &  \forall d \in D, t \in T\_{d}, j \in J  \\
& \sum\_{d \in D} \sum\_{(s,t) \in O\_{id}} x\_{idst} \leq B\_{i} &  \forall i \in I \\
 & y\_{idtj} - y\_{id,t-1,j} - y\_{id,t-1,r} \leq u\_{idtj} & \forall i \in I, d \in D, t \in T\_{d}, j \in J \\
 & \sum^{k+\beta}\_{k'=k} y\_{idk'r} \leq \beta\left(1 - \sum\_{(s,t):s=k ,(s,t) \in O\_{id}} x\_{idst}\right)  & \forall i \in I, d \in D,  k \in T'\_{d} \\
 & \sum^{k}\_{k'=k-\beta} y\_{idk'r} \leq \beta\left(1 - \sum\_{(s,t):t=k ,(s,t) \in O\_{id}} x\_{idst}\right) & \forall i \in I, d \in D, k \in T'\_{d} 　\\
 & \sum\_{d \in D_w} \sum\_{(s,t) \in O\_{id}} w\_{ist} x\_{idst} \leq 40 &  \forall i \in I, w \in W \\
\end{array}
$$

目的関数は費用最小化である. - 第1項は、スタッフに支払う賃金を表す. -
第2項は、人員不足時のペナルティ費用を表す. -
第３項は、業務変更の費用を表す。

制約： -
変数*y*<sub>*i**d**t**j*</sub>と変数*x*<sub>*i**d**s**t*</sub>の繋がりを表す制約 -
1計画日中に出勤・退勤は高々1度しかないことを表す制約 -
勤務期間内において労働時間に応じた休憩を取らなければならないことを表す制約 -
日*d*期*t*における業務*j*のスタッフの必要人数を満たすことを表す制約 -
スタッフ*i*の労働期数の上限を表す制約 - 休憩 (添字 *r*)
以外からの業務変更を行うときの変数 *u*<sub>*i**d**t**j*</sub> と変数
*y*<sub>*i**d**t**j*</sub> の繋ぎ式 -
勤務開始直後・終了直前における休憩を禁止する制約 -
週あたりの稼働時間が法定の40時間を超えないことを表す制約（未実装）

## データ生成

日データ day_df （ファイル名はday.csv）

列： - id : 0から始まる整数 - day : 日付 - day_of_week :
曜日；holidaysパッケージを用いて日本の祝日の場合には Holidayを入れる。 -
day_type:
人数の必要量データは、この列の要素ごとに定義される。ここでは、平日
(weekday)、日曜 (sunday)と祝日(holiday)の3種類を準備する。

### 日データ生成関数 generate_day

引数： - start_date: 開始日を表す文字列 - end_date: 終了日を表す文字列

返値：

day_df: 日データフレーム

    <unknown>:349: SyntaxWarning: invalid escape sequence '\]'

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L46"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_day

``` python

def generate_day(
    start_date, end_date
):

```

*日データ day_df 生成*

### generate_day関数の使用例

``` python
day_df = generate_day('2020-5-1', '2020-5-15')
#day_df.to_csv(folder+"day.csv")    
day_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">id</th>
<th data-quarto-table-cell-role="th">day</th>
<th data-quarto-table-cell-role="th">day_of_week</th>
<th data-quarto-table-cell-role="th">day_type</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0</td>
<td>2020-05-01</td>
<td>Fri</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>1</td>
<td>2020-05-02</td>
<td>Sat</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>2</td>
<td>2020-05-03</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>3</td>
<td>2020-05-04</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>4</td>
<td>2020-05-05</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>5</td>
<td>2020-05-06</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>6</td>
<td>2020-05-07</td>
<td>Thu</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>7</td>
<td>2020-05-08</td>
<td>Fri</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>8</td>
<td>2020-05-09</td>
<td>Sat</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>9</td>
<td>2020-05-10</td>
<td>Sun</td>
<td>sunday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>10</td>
<td>2020-05-11</td>
<td>Mon</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>11</td>
<td>2020-05-12</td>
<td>Tue</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">12</td>
<td>12</td>
<td>2020-05-13</td>
<td>Wed</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">13</td>
<td>13</td>
<td>2020-05-14</td>
<td>Thu</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">14</td>
<td>14</td>
<td>2020-05-15</td>
<td>Fri</td>
<td>weekday</td>
</tr>
</tbody>
</table>

</div>

``` python
jp_holidays = holidays.Japan()
#dt.date(2015, 1, 1) in jp_holidays 
#print(jp_holidays)
day_df = pd.DataFrame(pd.date_range('2020-5-1', '2020-5-15', freq='D'),columns=["day"])
day_df["day_of_week"] = [('Holiday') if  t in jp_holidays else (t.strftime('%a')) for t in day_df["day"] ]
n_day = len(day_df)

row_ = []
for row in day_df.itertuples():
    if row.day_of_week =="Holiday":
        row_.append("holiday")
    elif row.day_of_week =="Sun":
        row_.append("sunday")
    else:
        row_.append("weekday")
day_df["day_type"] = row_
day_df["id"] = [t for t in range(len(day_df))] 
day_df = day_df.reindex(columns=["id", "day", "day_of_week", "day_type"])
day_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">id</th>
<th data-quarto-table-cell-role="th">day</th>
<th data-quarto-table-cell-role="th">day_of_week</th>
<th data-quarto-table-cell-role="th">day_type</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0</td>
<td>2020-05-01</td>
<td>Fri</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>1</td>
<td>2020-05-02</td>
<td>Sat</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>2</td>
<td>2020-05-03</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>3</td>
<td>2020-05-04</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>4</td>
<td>2020-05-05</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>5</td>
<td>2020-05-06</td>
<td>Holiday</td>
<td>holiday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>6</td>
<td>2020-05-07</td>
<td>Thu</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>7</td>
<td>2020-05-08</td>
<td>Fri</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>8</td>
<td>2020-05-09</td>
<td>Sat</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>9</td>
<td>2020-05-10</td>
<td>Sun</td>
<td>sunday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>10</td>
<td>2020-05-11</td>
<td>Mon</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>11</td>
<td>2020-05-12</td>
<td>Tue</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">12</td>
<td>12</td>
<td>2020-05-13</td>
<td>Wed</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">13</td>
<td>13</td>
<td>2020-05-14</td>
<td>Thu</td>
<td>weekday</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">14</td>
<td>14</td>
<td>2020-05-15</td>
<td>Fri</td>
<td>weekday</td>
</tr>
</tbody>
</table>

</div>

### 期間データ period_df (ファイル名 period.csv)

1日のスケジュールは、基本時間単位の区間（これを期と呼ぶ）に対して決められる。
以下では、１時間を１期として生成するが、30分や15分でも構わない。
従来のシフトスケジューリングでは、6時間などを1期として扱うことが多かったが、本システムではより細かい時間単位を用いて最適化を行う。
これは、昨今の時間給で働く従業員が増えたことを考慮したものである。

列： - id : 0から始まる整数 - description:
時間区分の説明；期の開始時刻を入れる。例えば、9:00という説明の期は9:00から10:00の区間を表す。

### 期間データ生成関数 generate_period

引数： - start_time: 開始期の開始時刻 - end_time: 終了期の終了時刻 -
freq: 時間の刻みを表す文字列；
1時間（既定値）の場合は”1h”，30分の場合は”30min”と入力する．

返値： period_df: 期間データフレーム

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L69"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_period

``` python

def generate_period(
    start_time, end_time, freq:str='1h'
):

```

*期間データ生成関数 generate_period*

### generate_period関数の使用例

9時から21時まで１時間刻みで期データを生成する．
実際には０から11期までの12期分が計画期間になるが，終了時刻を表す21時(１２期)が最後に追加されている．これはガントチャートを描画するときに用いるだけである．

``` python
#start_time = pd.to_datetime("9:00")
#end_time = pd.to_datetime("21:00")
start_time ="9:00"
end_time = "21:00"
period_df = generate_period(start_time, end_time, freq="1h")
period_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">id</th>
<th data-quarto-table-cell-role="th">description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0</td>
<td>09:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>1</td>
<td>10:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>2</td>
<td>11:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>3</td>
<td>12:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>4</td>
<td>13:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>5</td>
<td>14:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>6</td>
<td>15:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>7</td>
<td>16:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>8</td>
<td>17:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>9</td>
<td>18:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>10</td>
<td>19:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>11</td>
<td>20:00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">12</td>
<td>12</td>
<td>21:00</td>
</tr>
</tbody>
</table>

</div>

### 休憩データ break_df （ファイル名は break.csv)

時間ごとのシフトスケジュールを組む際には、休憩を考慮することも重要になる。
ここでは、就業規則を反映した休憩データを準備するものとする。
これは、1日の稼働時間に対して、何期分の休憩を入れるかを定義したものである。
また、シフトの開始から（もしくは終了前の）何期の間は休憩を入れることができないといった制約も加える。

列： - period :
1日のシフトの稼働時間（最低稼働期間は最初の期；以下の例では3期） -
break_time : 休憩を行う期の数

``` python
# break.csv
random.seed(1)
T = 13
break_prob = 0.3
period_, break_ = [], []
min_work_time = 3
for t in range(min_work_time, T):
    period_.append(t)
    if t == min_work_time:
        break_ = [0]
    else:
        if random.random() <= break_prob:
            break_.append(break_[-1] + 1)
        else:
            break_.append(break_[-1])
break_df = pd.DataFrame({"period":period_, "break_time":break_})
#break_df.to_csv(folder + "break.csv")
break_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">period</th>
<th data-quarto-table-cell-role="th">break_time</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>3</td>
<td>0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>4</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>5</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>6</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>7</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>8</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>9</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>10</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>12</td>
<td>3</td>
</tr>
</tbody>
</table>

</div>

### 休憩時間設定用のExcelファイルを生成する関数 generate_break_excel

引数： - start_time: 開始期の開始時刻 - end_time: 終了期の終了時刻 -
freq: 時間の刻みを表す文字列；
1時間（既定値）の場合は”1h”，30分の場合は”30min”と入力する．

返値： wb: ExcelのWorkBookインスタンス

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L80"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_break_excel

``` python

def generate_break_excel(
    start_time, end_time, freq
):

```

*Call self as a function.*

#### generate_break_excel関数の使用例

``` python
start_time = pd.to_datetime("9:00")
end_time = pd.to_datetime("13:00")
freq="30min"
wb = generate_break_excel(start_time, end_time, freq)

wb.save("break.xlsx")
```

### 必要人数設定用のExcelファイルを生成する関数 generate_requirement_excel

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L96"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_requirement_excel

``` python

def generate_requirement_excel(
    start_time, end_time, freq, job_list
):

```

*Call self as a function.*

``` python
start_time = pd.to_datetime("9:00")
end_time = pd.to_datetime("13:00")
freq="30min"
job_list= ["レジ打ち", "バックヤード", "接客", "調理"]
wb = generate_requirement_excel(start_time, end_time, freq, job_list)
wb.save("requirement.xlsx")
```

### 日タイプ情報設定用のExcelファイルを生成する関数 generate_day_excel

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L117"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_day_excel

``` python

def generate_day_excel(
    start_date, end_date
):

```

*日データ day_df 生成*

``` python
wb = generate_day_excel('2020-5-1', '2020-5-15')
wb.save("day.xlsx")
```

### スタッフ情報設定用のExcelファイルを生成する関数 generate_staff_excel

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L143"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_staff_excel

``` python

def generate_staff_excel(
    job_list
):

```

*Call self as a function.*

``` python
job_list = ["レジ打ち", "バックヤード", "接客", "調理"]
wb = generate_staff_excel(job_list)
wb.save("staff.xlsx")
```

### ジョブ（業務）データ job_df （ファイル名は job_csv)

列： - id: : 0から始まる整数 - description :
ジョブ（業務、仕事）の名称；最初の行（idは0）には必ず休憩を表す”break”を入れておく。

``` python
# job.csv
#description_ = ["break", レジ打ち", "バックヤード", "接客", "調理"]
description_ = ["break", "レジ打ち", "接客"]
n_job = len(description_)
id_ = [t for t in range(n_job)]
job_df = pd.DataFrame({"id":id_, "description":description_})
#job_df.to_csv(folder + "job.csv")
job_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">id</th>
<th data-quarto-table-cell-role="th">description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0</td>
<td>break</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>1</td>
<td>レジ打ち</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>2</td>
<td>接客</td>
</tr>
</tbody>
</table>

</div>

### スタッフ（従業員）データ staff_df （ファイル名は staff.csv)

列： - name : スタッフの名前 - wage_per_period : 1期あたりの賃金 -
max_period : 1日あたりの最大稼働時間 - max_day
:　計画期間内に出勤できる最大日数 - job_set :
スタッフに割り当てることが可能なジョブ（業務）の集合；ジョブidをリスト形式の文字列で入力する。 -
day_off : 出勤できない日のidをリスト形式の文字列で入力する。 - start:
出勤可能な最早期id - end: 退勤する最遅期id

``` python
# staff.csv
fake = Faker(['en_US', 'ja_JP','zh_CN','ko_KR'])
Faker.seed(1)

n_day = len(day_df)
n_job = len(job_df)
n_staff = 30
name_ = []
job_list = list(job_df["id"][1:]) #最初のジョブは休憩なので除く
for i in range(n_staff):
    name_.append( fake.name() )

staff_df = pd.DataFrame( {"name": name_, 
                          "wage_per_period": np.random.randint(low=850,high=1300,size=n_staff),
                          "max_period": np.random.randint(5,break_df.period.max()+1, n_staff),
                          "max_day": np.random.randint(1,3, n_staff),
                          "job_set": [ str(random.sample(job_list,random.randint(1,n_job-1) )) for s in range(n_staff) ],
                          "day_off": [ str(random.sample( list(range(n_day)), 1 )) for s in range(n_staff) ],
                          #"day_off": [ "[]" for s in range(n_staff) ],
                          "start": np.random.randint(low=0, high=len(period_df)//2 -1, size= n_staff),
                           "end": np.random.randint(low=len(period_df)//2 + 1, high=len(period_df)-1, size= n_staff)
                         } )

#staff_df.to_csv(folder+"staff.csv")
staff_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th">wage_per_period</th>
<th data-quarto-table-cell-role="th">max_period</th>
<th data-quarto-table-cell-role="th">max_day</th>
<th data-quarto-table-cell-role="th">job_set</th>
<th data-quarto-table-cell-role="th">day_off</th>
<th data-quarto-table-cell-role="th">start</th>
<th data-quarto-table-cell-role="th">end</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>近藤 裕樹</td>
<td>1255</td>
<td>7</td>
<td>2</td>
<td>[3, 4]</td>
<td>[11]</td>
<td>0</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>Jon Cole</td>
<td>1296</td>
<td>8</td>
<td>2</td>
<td>[4]</td>
<td>[9]</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>Rachel Davis</td>
<td>1121</td>
<td>8</td>
<td>1</td>
<td>[4, 3, 1]</td>
<td>[5]</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>村上 拓真</td>
<td>1035</td>
<td>7</td>
<td>2</td>
<td>[1]</td>
<td>[7]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>김영희</td>
<td>1205</td>
<td>7</td>
<td>2</td>
<td>[2]</td>
<td>[6]</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>伊藤 陽子</td>
<td>991</td>
<td>5</td>
<td>1</td>
<td>[2, 3]</td>
<td>[5]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>Amanda Johnson</td>
<td>965</td>
<td>6</td>
<td>2</td>
<td>[3, 2]</td>
<td>[6]</td>
<td>0</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>심영미</td>
<td>924</td>
<td>7</td>
<td>1</td>
<td>[3, 2, 4]</td>
<td>[1]</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>吉田 晃</td>
<td>1023</td>
<td>8</td>
<td>1</td>
<td>[3]</td>
<td>[1]</td>
<td>2</td>
<td>5</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>徐娟</td>
<td>1087</td>
<td>5</td>
<td>2</td>
<td>[4, 1]</td>
<td>[14]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>David Robinson</td>
<td>927</td>
<td>5</td>
<td>2</td>
<td>[3]</td>
<td>[5]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>山本 晃</td>
<td>1094</td>
<td>6</td>
<td>1</td>
<td>[4]</td>
<td>[9]</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">12</td>
<td>최은주</td>
<td>943</td>
<td>5</td>
<td>1</td>
<td>[4]</td>
<td>[7]</td>
<td>0</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">13</td>
<td>中川 陽一</td>
<td>1243</td>
<td>6</td>
<td>2</td>
<td>[2, 4]</td>
<td>[1]</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">14</td>
<td>이정웅</td>
<td>1214</td>
<td>6</td>
<td>1</td>
<td>[4]</td>
<td>[4]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">15</td>
<td>Taylor Henderson</td>
<td>868</td>
<td>7</td>
<td>1</td>
<td>[2]</td>
<td>[3]</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">16</td>
<td>장수진</td>
<td>1202</td>
<td>5</td>
<td>1</td>
<td>[3]</td>
<td>[12]</td>
<td>2</td>
<td>5</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">17</td>
<td>文雪梅</td>
<td>1026</td>
<td>6</td>
<td>1</td>
<td>[3, 4, 1]</td>
<td>[9]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">18</td>
<td>최경희</td>
<td>1052</td>
<td>6</td>
<td>2</td>
<td>[3, 1, 4, 2]</td>
<td>[12]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">19</td>
<td>Robert Perry</td>
<td>1021</td>
<td>6</td>
<td>1</td>
<td>[1]</td>
<td>[14]</td>
<td>0</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">20</td>
<td>高畅</td>
<td>1101</td>
<td>6</td>
<td>2</td>
<td>[4]</td>
<td>[8]</td>
<td>0</td>
<td>5</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">21</td>
<td>이영희</td>
<td>1253</td>
<td>8</td>
<td>2</td>
<td>[1]</td>
<td>[13]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">22</td>
<td>石玲</td>
<td>1162</td>
<td>5</td>
<td>1</td>
<td>[2, 3]</td>
<td>[11]</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">23</td>
<td>John Johnson</td>
<td>898</td>
<td>6</td>
<td>1</td>
<td>[2, 1, 4, 3]</td>
<td>[7]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">24</td>
<td>橋本 零</td>
<td>962</td>
<td>7</td>
<td>1</td>
<td>[2, 3]</td>
<td>[10]</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">25</td>
<td>加藤 加奈</td>
<td>985</td>
<td>5</td>
<td>1</td>
<td>[4]</td>
<td>[5]</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">26</td>
<td>鞠秀荣</td>
<td>1166</td>
<td>6</td>
<td>1</td>
<td>[3, 4, 2, 1]</td>
<td>[4]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">27</td>
<td>石井 くみ子</td>
<td>1037</td>
<td>7</td>
<td>2</td>
<td>[1, 4, 2]</td>
<td>[2]</td>
<td>2</td>
<td>6</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">28</td>
<td>解英</td>
<td>1091</td>
<td>6</td>
<td>2</td>
<td>[1]</td>
<td>[8]</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">29</td>
<td>이영철</td>
<td>1087</td>
<td>7</td>
<td>2</td>
<td>[3]</td>
<td>[3]</td>
<td>0</td>
<td>7</td>
</tr>
</tbody>
</table>

</div>

### 必要人数データ requirement_df （ファイル名は requirement.csv)

列： - day_type : day_dfの
day_type列で入力した日の種類；この種類別に必要人数を定義する。 - job :
ジョブid　 - period : 期id - requirement : 必要人数

``` python
# requirement
n_period = len(period_df)-1
day_type = ["weekday", "sunday", "holiday"]
type_, job_, period_, lb_ = [],[],[],[] 
for d in day_type:
    for j in range(1,n_job): #ジョブ番号0は休憩なので除く
        req_ = np.ones(n_period, int)
        lb = 0
        ub = n_period
        for iter_ in range(4):
            lb = lb + random.randint(1, 3)
            ub = ub - random.randint(1, 3)
            if lb < ub:
                for t in range(lb,ub):
                    req_[t]+=1
            
        for t in range(n_period):
            type_.append(d)
            job_.append(j)
            period_.append(t)
            lb_.append(req_[t])

requirement_df = pd.DataFrame({"day_type":type_, "job":job_, "period":period_,"requirement":lb_ })
#requirement_df.to_csv(folder+"requirement.csv")
requirement_df.head()
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">day_type</th>
<th data-quarto-table-cell-role="th">job</th>
<th data-quarto-table-cell-role="th">period</th>
<th data-quarto-table-cell-role="th">requirement</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>weekday</td>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>weekday</td>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>weekday</td>
<td>1</td>
<td>2</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>weekday</td>
<td>1</td>
<td>3</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>weekday</td>
<td>1</td>
<td>4</td>
<td>3</td>
</tr>
</tbody>
</table>

</div>

## JSONデータをデータフレームに変換する関数 convert_shift_data

Streamlitでfirebaseのデータベースから得たJSON形式のデータを最適化関数の入力となるデータフレームに変換する．

引数： - day_json : 日JSONデータ - break_df : 休憩JSONデータ - staff_df
: スタッフJSONデータ - requirement_df : 必要人数JSONデータ -
min_work_periods: 最小稼働期間（既定値は1）

返値： - period_df : 期間データフレーム - break_df :
休憩データフレーム - day_df : 日データフレーム - job_df :
ジョブデータフレーム - staff_df : スタッフデータフレーム -
requirement_df : 必要人数データフレーム

``` python
with open("staff.json") as f:
    staff_json = f.read()
staff_data = pd.read_json(staff_json)
staff_df = staff_data[ ["ニックネーム", "優先度", "最大稼働期間", "最大出勤日数", "開始時刻", "終了時刻", "日別希望時間"] ]
#dic = staff_df.日別希望時間[1]
day_dic ={d:t  for d,t in zip(day_df.day, day_df.index)}
period_dic = {d:i for d,i in zip(period_df.description, period_df.id) }

request = []
for req in staff_df.日別希望時間:
    if req is None or len(req)==0: 
        request.append(None)
        continue    

    D ={}
    for key in req:
        try:
            D[ day_dic[key] ] = (period_dic.get(req[key][0],0), period_dic.get(req[key][0],period_df.id.max()-1) )
        except KeyError: #対応する日が計画期間内にない
            pass
    if len(D)>=1:
        request.append(str(D))
    else:
        request.append(None)
```

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L167"
target="_blank" style="float:right; font-size:smaller">source</a>

### convert_shift_data

``` python

def convert_shift_data(
    day_json, break_json, staff_json, requirement_json, min_work_periods:int=1
):

```

*Call self as a function.*

#### convert_shift_data関数の使用例

``` python
# day_df = pd.read_excel("day.xlsx") #id, day, day_of_week, day_type
# break_df = pd.read_excel("break.xlsx") #id,description
# staff_data = pd.read_csv("staff.csv") #name (=nickname), wage_per_period (=priority), max_period, max_day, job_set, day_off, start, end
# requirement_dic = pd.read_excel("requirement.xlsx", sheet_name = None, header=1) 

import json
with open("day.json") as f:
    day_json = f.read()
with open("break.json") as f:
    break_json = f.read()
with open("staff.json") as f:
    staff_json = f.read()
with open("requirement.json") as f:
    requirement_json = f.read()
        
period_df, break_df, day_df, job_df, staff_df, requirement_df = convert_shift_data(day_json, break_json,
                                                                                   staff_json, requirement_json, min_work_periods = 3)

# cost_df, violate_df, new_staff_df, job_assign, status = shift_scheduling(period_df, break_df, day_df, job_df, staff_df, requirement_df, theta=1,
#                     lb_penalty =10000, ub_penalty =10000, job_change_penalty = 10, break_penalty = 10000, max_day_penalty = 5000, 
#                     OutputFlag=False, TimeLimit=1, random_seed = 2)
```

## アナリティクス

スタッフの情報と必要人数データから，大まかな実行可能性を調べる．

スタッフが確率的にジョブや時間に割り当てられたと仮定して，必要量との比率を求め，可視化する．

``` python
n_day = len(day_df)
n_job = len(job_df)
n_period = len(period_df)-1
work_hours = np.zeros( (n_job,n_day,n_period) )

for row in staff_df.itertuples():
    job_set = ast.literal_eval(row.job_set) 
    day_off = set( ast.literal_eval(row.day_off) )
    max_period = row.max_period  #最大稼働時間/１日の稼働時間 だけ加算する
    max_day = row.max_day #最大稼働日数/計画期間 だけ加算する．
    ratio = max_period/n_period * max_day/n_day
    #print(ratio)
    for d in range(n_day):
        if d not in day_off:
            for j in job_set:
                for t in range(row.start, row.end+1):
                    work_hours[j,d,t]+= ratio
                   
work_hours[1]
```

    array([[0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.        , 0.        , 0.        , 0.        , 0.        ,
            0.        , 0.        , 0.        ],
           [0.        , 0.        , 0.        , 0.        , 0.        ,
            0.        , 0.        , 0.        ],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.        , 0.        , 0.        , 0.        , 0.        ,
            0.        , 0.        , 0.        ],
           [0.        , 0.        , 0.        , 0.        , 0.        ,
            0.        , 0.        , 0.        ],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667],
           [0.66666667, 0.66666667, 0.66666667, 0.66666667, 0.66666667,
            0.66666667, 0.66666667, 0.66666667]])

``` python
requirement ={}
for row in requirement_df.itertuples():
    requirement[row.day_type, row.period, row.job] = row.requirement
req = np.zeros( (n_job, n_day, n_period) )
for d, row in enumerate(day_df.itertuples()):
    for j in range(1,n_job):
        for t in range(n_period):
            req[j,d,t] += requirement[row.day_type,t,j]
```

``` python
req[1]
```

    array([[1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1., 1., 1.]])

``` python
import seaborn as sns
sns.heatmap(work_hours[1]/req[1], annot=True, fmt="1.2f");
```

![](10shift_files/figure-commonmark/cell-31-output-1.png)

``` python
fig = px.imshow(work_hours[1]/req[1], color_continuous_scale=px.colors.sequential.Viridis)
fig.update_xaxes(side="top")
plotly.offline.plot(fig);
```

### スタッフの希望と必要人数の関係を調べる関数 estimate_requirement

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L262"
target="_blank" style="float:right; font-size:smaller">source</a>

### estimate_requirement

``` python

def estimate_requirement(
    day_df, period_df, job_df, staff_df, requirement_df, days:NoneType=None
):

```

*Call self as a function.*

``` python
fig = estimate_requirement(day_df, period_df, job_df, staff_df, requirement_df, days=[1,2,3])
#plotly.offline.plot(fig);
```

![](10shift_files/figure-commonmark/cell-35-output-1.png)

### 制約逸脱計算関数 evaluate_violation

引数： - violated （最適化で得られた逸脱量を表す辞書）

返値： - cost_df: 逸脱ペナルティを入れたデータフレーム

    <unknown>:20: SyntaxWarning: invalid escape sequence '\]'

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L330"
target="_blank" style="float:right; font-size:smaller">source</a>

### evaluate_violation

``` python

def evaluate_violation(
    violated, x, period_df, day_df, staff_df
):

```

*制約逸脱計算関数*

### evaluate_violation関数の使用例

shift_schedulingの中で使用する．

## SCOP Model

制約最適化ソルバー SCOP を用いたモデルを記述する。

引数：

- period_df : 期間データフレーム
- break_df : 休憩データフレーム
- day_df : 日データフレーム
- job_df : ジョブデータフレーム
- staff_df : スタッフデータフレーム
- requirement_df : 必要人数データフレーム
- theta :
  開始直後（もしくは終了直前）に休憩を禁止する期間数（既定値は1）
- lb_penalty : 必要人数を下回った場合のペナルティ（既定値は10000）
- ub_penalty : 必要人数を上回った場合のペナルティ（既定値は0）
- job_change_penalty : ジョブを切り替えたときのペナルティ（既定値は10）
- break_penalty :
  開始直後・終了直前の休憩を逸脱したときのペナルティ（既定値は10000）
- max_day_penalty :
  最大稼働日数を超過したときのペナルティ（既定値は5000）
- OutputFlag : 出力フラグ；ソルバーの出力を出す場合にはTrue
  （既定値はFalse）
- TimeLimit : 計算時間上限（既定値は10秒）
- random_seed : ソルバーで用いる擬似乱数の種（既定値は1)
- cloud: 複数人が同時実行する可能性があるときTrue（既定値はFalse）;
  Trueのとき，ソルバー呼び出し時に生成されるファイルにタイムスタンプを追加し，計算終了後にファイルを消去する．

返値： - x : 変数 *x* を入れた辞書 - y : 変数 *y* を入れた辞書 - sol :
解を表す辞書 - violated : 逸脱した制約を表す辞書 - new_staff_df :
スタッフデータフレームにシフトを追加したもの - job_assign:
スタッフに割り当てられたジョブの情報を保持した辞書 - status :
最適化の状態を表す数字；以下の意味を持つ。

<table>
<thead>
<tr>
<th>status</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>最適化成功</td>
</tr>
<tr>
<td>1</td>
<td>求解中にユーザが Ctrl-C を入力したことによって強制終了した．</td>
</tr>
<tr>
<td>2</td>
<td>入力データファイルの読み込みに失敗した．</td>
</tr>
<tr>
<td>3</td>
<td>初期解ファイルの読み込みに失敗した．</td>
</tr>
<tr>
<td>4</td>
<td>ログファイルの書き込みに失敗した．</td>
</tr>
<tr>
<td>5</td>
<td>入力データの書式にエラーがある．</td>
</tr>
<tr>
<td>6</td>
<td>メモリの確保に失敗した．</td>
</tr>
<tr>
<td>7</td>
<td>実行ファイル scop.exe のよび出しに失敗した．</td>
</tr>
<tr>
<td>10</td>
<td>モデルの入力は完了しているが，まだ最適化されていない．</td>
</tr>
<tr>
<td>負の値</td>
<td>その他のエラー</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L381"
target="_blank" style="float:right; font-size:smaller">source</a>

### shift_scheduling

``` python

def shift_scheduling(
    period_df, break_df, day_df, job_df, staff_df, requirement_df, theta:int=1, lb_penalty:int=10000,
    ub_penalty:int=0, job_change_penalty:int=10, break_penalty:int=10000, max_day_penalty:int=5000,
    OutputFlag:bool=False, TimeLimit:int=10, random_seed:int=1, cloud:bool=False
):

```

*シフト最適化*

## SCOP（日別リクエスト）モデル

staff_dfに request列を追加

希望日をキー，開始，終了時刻を値

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L625"
target="_blank" style="float:right; font-size:smaller">source</a>

### shift_scheduling2

``` python

def shift_scheduling2(
    period_df, break_df, day_df, job_df, staff_df, requirement_df, theta:int=1, lb_penalty:int=10000,
    ub_penalty:int=0, job_change_penalty:int=10, break_penalty:int=10000, max_day_penalty:int=5000,
    OutputFlag:bool=False, TimeLimit:int=10, random_seed:int=1, cloud:bool=False
):

```

*シフト最適化（スタッフが日毎に開始希望時刻と終了希望時刻を入れられるように変更）*

``` python
cost_df, violate_df, new_staff_df, job_assign, status = shift_scheduling2(period_df, break_df, day_df, job_df, staff_df, requirement_df, theta=1,
                    lb_penalty =10000, ub_penalty =10000, job_change_penalty = 10, break_penalty = 10000, max_day_penalty = 5000, 
                    OutputFlag=False, TimeLimit=30, random_seed = 2)
```


     ================ Now solving the problem ================ 

``` python
cost_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">penalty</th>
<th data-quarto-table-cell-role="th">value</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>Cost</td>
<td>67288</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>Staff Lower Bound</td>
<td>0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>Staff Upper Bound</td>
<td>105</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>Change Job</td>
<td>46</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>Break Number</td>
<td>0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>Early Break</td>
<td>3</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>Late Break</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>Max Work Day</td>
<td>0</td>
</tr>
</tbody>
</table>

</div>

### shift_scheduling関数の使用例

``` python
# period_df = pd.read_csv(folder+"period.csv", index_col=0)
# break_df = pd.read_csv(folder+"break.csv", index_col=0)
# day_df = pd.read_csv(folder+"day.csv", index_col=0)
# job_df = pd.read_csv(folder+"job.csv", index_col=0)
# staff_df = pd.read_csv(folder+"staff.csv", index_col=0)
# requirement_df = pd.read_csv(folder+"requirement.csv", index_col=0)

cost_df, violate_df, new_staff_df, job_assign, status = shift_scheduling(period_df, break_df, day_df, job_df, staff_df, requirement_df, theta=1,
                    lb_penalty =10000, ub_penalty =10000, job_change_penalty = 10, break_penalty = 10000, max_day_penalty = 5000, 
                    OutputFlag=False, TimeLimit=30, random_seed = 2)
```


     ================ Now solving the problem ================ 

``` python
cost_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">penalty</th>
<th data-quarto-table-cell-role="th">value</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>Cost</td>
<td>68306</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>Staff Lower Bound</td>
<td>2</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>Staff Upper Bound</td>
<td>143</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>Change Job</td>
<td>25</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>Break Number</td>
<td>0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>Early Break</td>
<td>5</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>Late Break</td>
<td>3</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>Max Work Day</td>
<td>0</td>
</tr>
</tbody>
</table>

</div>

``` python
violate_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">Day 0</th>
<th data-quarto-table-cell-role="th">Day 1</th>
<th data-quarto-table-cell-role="th">Day 2</th>
<th data-quarto-table-cell-role="th">Day 3</th>
<th data-quarto-table-cell-role="th">Day 4</th>
<th data-quarto-table-cell-role="th">Day 5</th>
<th data-quarto-table-cell-role="th">Day 6</th>
<th data-quarto-table-cell-role="th">Day 7</th>
<th data-quarto-table-cell-role="th">Day 8</th>
<th data-quarto-table-cell-role="th">Day 9</th>
<th data-quarto-table-cell-role="th">Day 10</th>
<th data-quarto-table-cell-role="th">Day 11</th>
<th data-quarto-table-cell-role="th">Day 12</th>
<th data-quarto-table-cell-role="th">Day 13</th>
<th data-quarto-table-cell-role="th">Day 14</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">Staff Lower Bound</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>2.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">Staff Upper Bound</td>
<td>6.0</td>
<td>13.0</td>
<td>8.0</td>
<td>1.0</td>
<td>6.0</td>
<td>7.0</td>
<td>11.0</td>
<td>19.0</td>
<td>3.0</td>
<td>25.0</td>
<td>8.0</td>
<td>5.0</td>
<td>2.0</td>
<td>14.0</td>
<td>15.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">Change Job</td>
<td>3.0</td>
<td>0.0</td>
<td>3.0</td>
<td>4.0</td>
<td>1.0</td>
<td>2.0</td>
<td>1.0</td>
<td>1.0</td>
<td>1.0</td>
<td>0.0</td>
<td>1.0</td>
<td>2.0</td>
<td>6.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">Break Number</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">Early Break</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>2.0</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">Late Break</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>1.0</td>
<td>0.0</td>
</tr>
</tbody>
</table>

</div>

``` python
#job_assign
```

``` python
#new_staff_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th">wage_per_period</th>
<th data-quarto-table-cell-role="th">max_period</th>
<th data-quarto-table-cell-role="th">max_day</th>
<th data-quarto-table-cell-role="th">job_set</th>
<th data-quarto-table-cell-role="th">day_off</th>
<th data-quarto-table-cell-role="th">start</th>
<th data-quarto-table-cell-role="th">end</th>
<th data-quarto-table-cell-role="th">Shift for Day 0</th>
<th data-quarto-table-cell-role="th">Shift for Day 1</th>
<th data-quarto-table-cell-role="th">Shift for Day 2</th>
<th data-quarto-table-cell-role="th">Shift for Day 3</th>
<th data-quarto-table-cell-role="th">Shift for Day 4</th>
<th data-quarto-table-cell-role="th">max day violation</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>Ryan Gallagher</td>
<td>908</td>
<td>10</td>
<td>13</td>
<td>[2]</td>
<td>[1]</td>
<td>2</td>
<td>7</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>3_7</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>박영길</td>
<td>1284</td>
<td>11</td>
<td>10</td>
<td>[2, 1]</td>
<td>[4]</td>
<td>2</td>
<td>7</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>Rachel Davis</td>
<td>1226</td>
<td>9</td>
<td>12</td>
<td>[2, 1]</td>
<td>[2]</td>
<td>3</td>
<td>8</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>史建平</td>
<td>1124</td>
<td>8</td>
<td>10</td>
<td>[2, 1]</td>
<td>[2]</td>
<td>3</td>
<td>10</td>
<td>7_9</td>
<td>4_6</td>
<td>None</td>
<td>5_10</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>김영희</td>
<td>972</td>
<td>12</td>
<td>10</td>
<td>[2, 1]</td>
<td>[1]</td>
<td>4</td>
<td>8</td>
<td>4_6</td>
<td>None</td>
<td>None</td>
<td>4_6</td>
<td>4_6</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>김현정</td>
<td>936</td>
<td>9</td>
<td>13</td>
<td>[1]</td>
<td>[3]</td>
<td>2</td>
<td>9</td>
<td>None</td>
<td>2_7</td>
<td>3_5</td>
<td>None</td>
<td>5_7</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>王洋</td>
<td>1123</td>
<td>10</td>
<td>12</td>
<td>[1]</td>
<td>[1]</td>
<td>3</td>
<td>9</td>
<td>None</td>
<td>None</td>
<td>6_8</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>Teresa James</td>
<td>1017</td>
<td>9</td>
<td>11</td>
<td>[1]</td>
<td>[1]</td>
<td>0</td>
<td>9</td>
<td>5_7</td>
<td>None</td>
<td>3_7</td>
<td>0_4</td>
<td>0_2</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>Javier Johnson</td>
<td>960</td>
<td>9</td>
<td>13</td>
<td>[1]</td>
<td>[2]</td>
<td>4</td>
<td>9</td>
<td>None</td>
<td>6_9</td>
<td>None</td>
<td>6_8</td>
<td>6_8</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>Jeffrey Simpson</td>
<td>911</td>
<td>8</td>
<td>10</td>
<td>[1, 2]</td>
<td>[1]</td>
<td>4</td>
<td>9</td>
<td>4_9</td>
<td>None</td>
<td>None</td>
<td>5_7</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>David Robinson</td>
<td>1021</td>
<td>11</td>
<td>13</td>
<td>[2]</td>
<td>[3]</td>
<td>1</td>
<td>8</td>
<td>5_7</td>
<td>1_3</td>
<td>3_5</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>이경수</td>
<td>1026</td>
<td>8</td>
<td>10</td>
<td>[1]</td>
<td>[4]</td>
<td>4</td>
<td>11</td>
<td>None</td>
<td>4_6</td>
<td>4_6</td>
<td>4_6</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">12</td>
<td>罗玉英</td>
<td>1220</td>
<td>12</td>
<td>12</td>
<td>[2]</td>
<td>[4]</td>
<td>3</td>
<td>9</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">13</td>
<td>진준호</td>
<td>1194</td>
<td>8</td>
<td>14</td>
<td>[1]</td>
<td>[0]</td>
<td>0</td>
<td>11</td>
<td>None</td>
<td>None</td>
<td>0_2</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">14</td>
<td>杨洁</td>
<td>1201</td>
<td>10</td>
<td>14</td>
<td>[2, 1]</td>
<td>[4]</td>
<td>1</td>
<td>8</td>
<td>4_6</td>
<td>5_7</td>
<td>5_7</td>
<td>3_5</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">15</td>
<td>谷红霞</td>
<td>1183</td>
<td>9</td>
<td>14</td>
<td>[2]</td>
<td>[4]</td>
<td>0</td>
<td>10</td>
<td>4_6</td>
<td>None</td>
<td>0_2</td>
<td>2_4</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">16</td>
<td>장수진</td>
<td>1287</td>
<td>10</td>
<td>13</td>
<td>[2]</td>
<td>[4]</td>
<td>3</td>
<td>11</td>
<td>8_10</td>
<td>7_9</td>
<td>7_11</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">17</td>
<td>文雪梅</td>
<td>1142</td>
<td>9</td>
<td>10</td>
<td>[1, 2]</td>
<td>[1]</td>
<td>0</td>
<td>10</td>
<td>1_3</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">18</td>
<td>최경희</td>
<td>943</td>
<td>11</td>
<td>14</td>
<td>[1]</td>
<td>[3]</td>
<td>4</td>
<td>11</td>
<td>8_11</td>
<td>8_11</td>
<td>8_10</td>
<td>None</td>
<td>7_11</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">19</td>
<td>안승현</td>
<td>1168</td>
<td>10</td>
<td>10</td>
<td>[2]</td>
<td>[2]</td>
<td>2</td>
<td>7</td>
<td>2_4</td>
<td>4_6</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">20</td>
<td>Joshua Warner</td>
<td>1237</td>
<td>8</td>
<td>11</td>
<td>[2]</td>
<td>[2]</td>
<td>2</td>
<td>11</td>
<td>6_9</td>
<td>6_11</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">21</td>
<td>이영희</td>
<td>1121</td>
<td>9</td>
<td>14</td>
<td>[2, 1]</td>
<td>[3]</td>
<td>2</td>
<td>10</td>
<td>None</td>
<td>5_7</td>
<td>4_6</td>
<td>None</td>
<td>3_5</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">22</td>
<td>石玲</td>
<td>993</td>
<td>11</td>
<td>12</td>
<td>[2]</td>
<td>[2]</td>
<td>0</td>
<td>11</td>
<td>0_2</td>
<td>0_2</td>
<td>None</td>
<td>0_4</td>
<td>0_3</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">23</td>
<td>杉山 晃</td>
<td>1199</td>
<td>8</td>
<td>13</td>
<td>[1]</td>
<td>[2]</td>
<td>0</td>
<td>11</td>
<td>1_3</td>
<td>None</td>
<td>None</td>
<td>7_11</td>
<td>1_6</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">24</td>
<td>한현준</td>
<td>1243</td>
<td>11</td>
<td>11</td>
<td>[1, 2]</td>
<td>[3]</td>
<td>0</td>
<td>10</td>
<td>7_9</td>
<td>7_9</td>
<td>None</td>
<td>None</td>
<td>5_10</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">25</td>
<td>佐々木 加奈</td>
<td>1063</td>
<td>12</td>
<td>13</td>
<td>[2, 1]</td>
<td>[1]</td>
<td>4</td>
<td>11</td>
<td>7_11</td>
<td>None</td>
<td>None</td>
<td>7_11</td>
<td>4_6</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">26</td>
<td>Vicki Townsend</td>
<td>985</td>
<td>8</td>
<td>13</td>
<td>[1, 2]</td>
<td>[3]</td>
<td>3</td>
<td>7</td>
<td>None</td>
<td>3_7</td>
<td>None</td>
<td>None</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">27</td>
<td>山岸 くみ子</td>
<td>1144</td>
<td>10</td>
<td>14</td>
<td>[1, 2]</td>
<td>[2]</td>
<td>0</td>
<td>11</td>
<td>0_5</td>
<td>0_3</td>
<td>None</td>
<td>1_3</td>
<td>8_11</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">28</td>
<td>解英</td>
<td>1056</td>
<td>10</td>
<td>10</td>
<td>[2, 1]</td>
<td>[4]</td>
<td>2</td>
<td>11</td>
<td>None</td>
<td>8_10</td>
<td>6_11</td>
<td>5_7</td>
<td>None</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">29</td>
<td>陈萍</td>
<td>1015</td>
<td>11</td>
<td>12</td>
<td>[2, 1]</td>
<td>[3]</td>
<td>1</td>
<td>9</td>
<td>None</td>
<td>1_4</td>
<td>1_6</td>
<td>None</td>
<td>2_4</td>
<td>0.0</td>
</tr>
</tbody>
</table>

</div>

``` python
#fig = make_requirement_graph(day_df, period_df, job_df, staff_df, requirement_df, job_assign, day=1)
#plotly.offline.plot(fig);
```

``` python
#fig = make_gannt_for_shift(day_df, period_df, staff_df, job_df, job_assign, day=1 )
#plotly.offline.plot(fig);
```

## Excel出力

### 日ごとのジョブのガントチャートのExcel Workbookを生成する関数 make_gannt_excel

引数： - job_assign:
スタッフに割り当てられたジョブの情報を保持した辞書 - period_df :
期間データフレーム - day_df : 日データフレーム - job_df :
ジョブデータフレーム - staff_df : スタッフデータフレーム -
requirement_df : 必要人数データフレーム

返値： wb: Excel (OptnPyXL) のWorkBookオブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L883"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_gannt_excel

``` python

def make_gannt_excel(
    job_assign, period_df, day_df, job_df, staff_df, requirement_df
):

```

*Call self as a function.*

#### make_gannt_excel関数の使用例

``` python
wb = make_gannt_excel(job_assign, period_df, day_df, job_df, staff_df, requirement_df)
wb.save("shift_gannt.xlsx")
```

### 期間内のシフト一覧を出力する関数 make_allshift_excel

引数： - new_staff_df : スタッフデータフレーム（シフトを追加したもの） -
day_df : 日データフレーム

返値： wb: Excel (OptnPyXL) のWorkBookオブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L966"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_allshift_excel

``` python

def make_allshift_excel(
    new_staff_df, day_df, period_df
):

```

*Call self as a function.*

#### make_allshift_excel関数の使用例

``` python
wb = make_allshift_excel(new_staff_df, day_df, period_df)
wb.save("all_shift.xlsx")
```

``` python
pd.read_excel("all_shift.xlsx")
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">Unnamed: 0</th>
<th data-quarto-table-cell-role="th">Unnamed: 1</th>
<th data-quarto-table-cell-role="th">2020-05-01</th>
<th data-quarto-table-cell-role="th">2020-05-02</th>
<th data-quarto-table-cell-role="th">2020-05-03</th>
<th data-quarto-table-cell-role="th">2020-05-04</th>
<th data-quarto-table-cell-role="th">2020-05-05</th>
<th data-quarto-table-cell-role="th">合計</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0.0</td>
<td>Ryan Gallagher</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>12:00</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>16:00</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>NaN</td>
<td>NaN</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>4</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>1.0</td>
<td>박영길</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">85</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>19:00</td>
<td>20:00</td>
<td>16:00</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">86</td>
<td>NaN</td>
<td>NaN</td>
<td>0</td>
<td>2</td>
<td>5</td>
<td>2</td>
<td>0</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">87</td>
<td>29.0</td>
<td>陈萍</td>
<td>NaN</td>
<td>10:00</td>
<td>10:00</td>
<td>NaN</td>
<td>11:00</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">88</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>13:00</td>
<td>15:00</td>
<td>NaN</td>
<td>13:00</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">89</td>
<td>NaN</td>
<td>NaN</td>
<td>0</td>
<td>3</td>
<td>5</td>
<td>0</td>
<td>2</td>
<td>NaN</td>
</tr>
</tbody>
</table>

<p>90 rows × 8 columns</p>
</div>

## 可視化

### 必要人数を描画する関数 make_requirement_graph

引数： - day_df : 日データフレーム - period_df : 期間データフレーム -
job_df : ジョブデータフレーム - staff_df : スタッフデータフレーム -
requirement_df : 必要人数データフレーム - job_assign : 変数 *y*
の情報（スタッフiが日dの期tに割り当てられたジョブの番号）を入れた辞書（キーは文字列） -
day : 描画したい日のid

返値： - fig : Plotlyのグラフオブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L1016"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_requirement_graph

``` python

def make_requirement_graph(
    day_df, period_df, job_df, staff_df, requirement_df, job_assign, day:int=0
):

```

*必要人数のグラフを生成する関数*

### make_requirement_graphの使用例

``` python
print("Status",status)
if status ==0: #SCOPが失敗していないときのみ表示
    fig = make_requirement_graph(day_df, period_df, job_df, staff_df, requirement_df, job_assign, day = 0)
    plotly.offline.plot(fig);
```

![](10shift_files/figure-commonmark/cell-55-output-1.png)

### ガントチャートを描画する関数 make_gannt_for_shift

引数： - day_df : 日データフレーム - period_df : 期間データフレーム -
staff_df : スタッフデータフレーム - job_df : ジョブデータフレーム -
requirement_df : 必要人数データフレーム - job_assign : 変数 *y*
の情報（スタッフiが日dの期tに割り当てられたジョブの番号）を入れた辞書（キーは文字列） -
day_off : 希望休日を入れた辞書 - day : 描画したい日のid

返値： - fig : Plotlyのグラフオブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L1092"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_gannt_for_shift

``` python

def make_gannt_for_shift(
    day_df, period_df, staff_df, job_df, job_assign, day:int=0
):

```

*スタッフごとのガントチャートを生成する関数*

### make_gannt_for_shiftの使用例

``` python
if status ==0: #SCOPが失敗していないときのみ表示
    fig = make_gannt_for_shift(day_df, period_df, staff_df, job_df, job_assign, day=0 )
    plotly.offline.plot(fig);
```

![](10shift_files/figure-commonmark/cell-58-output-1.png)

### 計算の過程を表示する関数 lot_scop_for_shift

引数：

- file_name: SCOPの出力ファイル名；既定値は “scop_out.txt”

返値： - Plotlyの図オブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/shift.py#L1169"
target="_blank" style="float:right; font-size:smaller">source</a>

### plot_scop_for_shift

``` python

def plot_scop_for_shift(
    file_name:str='scop_out.txt'
):

```

*Call self as a function.*

### plot_scop_for_shift関数の使用例

``` python
fig = plot_scop_for_shift("scop_out.txt")
plotly.offline.plot(fig);
```
