[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『複数ブックの同セルから数値を参照し平均を出す』(おうぶ)
毎朝、前日の売上等が記載されたExcelファイルを保存しております。 (例:0101.xlsm〜12/31.xlsm)
例えば、ある期間中の数値の平均を出したいときは、 「1つずつブックを開く」 「数値をまとめ」 「average関数を使い計算する」 というフローとなっているのですが、
それを、計算用のブックを1つ作成し、 「計算用ブックにて、期間を選択することで」 「その期間中の平均が表示される」 というものを作りたいと思っています。
分かりずらいかと思いますが、以下に詳しく記載しておりますので、 ご指導いただければ幸いです。
<<フォルダ構成>> A[FOLDER] │ ┝計算[FILE] └2020[FOLDER]┐ ┝ 0101[FILE] ┝ │ ┝ 〜 ┝ │ └ 1231[FILE]
< 使用 Excel:Excel2016、使用 OS:Windows10 >
こんばんは ^^ マクロでしたら、みんな開いた方が簡単かも、 参照する事も可能なのでしょうが、なにか 不都合でも。。。いづれにいたしましても 概略はよく解るのですが、後は詳細、 読込ブックのどのシートのどのセルを 集計して 書込みブックのどのシートのどのセルに 平均を表示[書込み]するのか、等を解りやすく エクセルの表形式でご提示賜れば、 マクロで全て開いても良いのでしたら、私もお手 伝い出来るかもしれません、また私が 解らずとも、多数アドバイス等があると 思います。。。← 多分ですが。。。^^; でわでわ。。。m(_ _)m (隠居じーさん) 2021/02/17(水) 20:12
隠居じーさん様
ご返信ありがとうございます。 私は所謂ショッピングモールの運営事務所に勤めているのですが、
各テナント様より、 「全体の売上」「A店の売上」「B店の売上」など・・・ 一度に複数を聞かれることが多く、 各内容ごとに計算する手間を省ければなと思っておりました。
計算する際に、 「0101のファイルを開き数値を参照し閉じる」 「0102のファイルを開き数値を参照し閉じる」 のように繰り返し動作する形がまだ可能性あるのかなと思っております。
以下、読込ブックのレイアウトになります。 (数値は例です)
A B M AE 5 月予算 〜 同曜日 〜 客曜昨 6 全店合計 99.9 51.4 42.5 〜 40 A店 84.7 34.4 18.5 〜 150 B店 79.9 187.4 49.5
以下、書込みブックのレイアウト予想図です。 (数値は例です) A B M AE 1 [店選択▼][計算実行ボタン] 〜 4 月予算 〜 同曜日 〜 客曜昨 5 99.9 51.4 42.5
A1セルに関しては、ドロップダウンリストが理想です。 [読込ブック]のA6〜A150のリストが出る形。 このリストに関しては、[書込みブック]の別シートで指定してあげる形 でもいいのかなと思っております。 ただその場合、 [書込みA1セルにてA店が指定されている場合] [読込ブックにてA店の行の数値を参照する] という形が必要なのかと思います。
5行目の数値の部分に、計算された数値が入る形が希望です。
列の「〜」には、他にも様々な項目が 行の「〜」にも、様々な店の名前があります。
「1/1-1/31の全店合計の同曜日が知りたい」 と聞かれた際に、 「ブック[0101]〜[0131]の[M6セル]を参照し平均を計算した値」 が出るようにしたいです。 【ブック間の串刺し計算】といったイメージでしょうか。
(おうぶ) 2021/02/17(水) 20:54
↑1 について訂正いたします。
「1/1-1/31の全店合計が知りたい」 と聞かれた際に、 「ブック[0101]〜[0131]の[6列目]を参照し、A5〜AE5セルに、平均を計算した値」 が出るようにしたいです。 (おうぶ) 2021/02/17(水) 21:03
1/1-1/31とかの 期間設定は、どうされるご予定で 1書込みブック(計算)のいずれかのセル[セル、二個使用でシリアル値で年月日、 表示形式で任意の表示] 2inputbox使用 3フォーム ← 大層になるので非推奨^^; 1が一番簡単便利と思われます。。。A^^;
ただいま勘案中。。。纏まりましたら、また 連絡させて戴きます。他の回答者様のアドバイスも お待ちくださいませ。m(__)m (隠居じーさん) 2021/02/17(水) 21:54
(1)指定の期間(例:1/1〜1/31)から、開始日と終了日を取り出す (2)開始日〜終了日までのファイルを順番に開き (3)まとめ用のシートに【ファイル名】とともに開いたブックの対象シートのデータをコピペする (4)開いたブックを閉じる (5)(2)〜(4)を繰り返し
というマクロを作り、必要なデータを一旦1つのブック(シート)に集めてから必要なデータを抽出します。
(もこな2) 2021/02/18(木) 01:16
お二方ともお返事ありがとうございます。
もこな2様の案ですが、 指定の期間(例:1/1-1/31)の各ブックの全てのデータ(A1〜AE150)を[書込み用ブック]の別シートにコピー (1シートごとに150行ずつ増えていくということですよね?) そこから、店名ごとに抽出するセルを変える感じでしたら、頑張れば書けそうですが、イメージ違いますでしょうか?
隠居じーさん様 期間指定ですが、0101〜1231のファイル名になっている為、 例えば、0131〜0201など、数値が飛ぶ個所などありますが、 そこは特に問題なく続けて参照してくれるものかと思っておりましたが、軽く考えすぎでしょうか?
皆さまのご意見を参考にしながら、素人なりに考えましたが、
[計算用ブック]別シートに、指定区間(例:0101〜0131)のデータ全てをコピー貼付する (各ブック150行あるので、単純計算150行×指定区間の日数分) (今回ですと31日分なので、4650行(!!))
順番に張り付けしていった場合、 例えば、[0101]A店の行が5行目だとすれば、[0102]A店の行は155行目に来るはずですよね? average関数は、空白セルは計算しないと記憶しておりますので、
=average(B5,B5+150,B5+300〜〜以下略) を365日分書いておく感じでいけないでしょうか? +150〜〜の部分はもっと賢い書き方などあるような気もしますが。。。
素人ながらご意見してみましたがいかがでしょうか。
(おうぶ) 2021/02/18(木) 13:57
|[A] |[B] |[C] |[D] |[E] |[F] |[G] |[H] |[I] |[J] |[K] |[L] |[M] |[N] |[O] |[P] |[Q] |[R] |[S] |[T] |[U] |[V] |[W] |[X] |[Y] |[Z] |[AA] |[AB] |[AC] |[AD] |[AE] [1] | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | [2] | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | [3] | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | [4] | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | [5] |項目-1 |月予算|項目-3|項目-4|項目-5|項目-6|項目-7|項目-8|項目-9|項目-10|項目-11|項目-12|同曜日|項目-14|項目-15|項目-16|項目-17|項目-18|項目-19|項目-20|項目-21|項目-22|項目-23|項目-24|項目-25|項目-26|項目-27|項目-28|項目-29|項目-30|客曜昨 [6] |支店合計 |7246.1| 400| 700| 300| 800| 400| 400| 500| 200| 900| 500|7522.9| 400| 500| 500| 900| 800| 300| 700| 800| 400| 800| 700| 800| 900| 800| 300| 800| 900|7176.9 [7] |支店 - 001| 98.2| 100| 900| 100| 900| 100| 900| 700| 500| 100| 300| 7.2| 200| 600| 300| 500| 800| 100| 300| 300| 300| 800| 600| 500| 700| 600| 200| 700| 400| 57.9 [8] |支店 - 002| 8.6| 300| 500| 700| 900| 100| 100| 900| 300| 800| 500| 64.2| 800| 300| 600| 900| 800| 300| 500| 800| 900| 200| 900| 300| 100| 700| 500| 500| 600| 90.4 [9] |支店 - 003| 75.2| 900| 500| 200| 200| 700| 100| 900| 300| 300| 100| 78.4| 900| 500| 500| 200| 100| 700| 900| 600| 700| 200| 500| 100| 200| 300| 100| 500| 100| 69.2
↑ ↓
[150]|支店 -150| 20.7| 800| 300| 200| 700| 900| 300| 700| 700| 600| 800| 5.9| 200| 900| 100| 600| 500| 100| 100| 100| 700| 700| 100| 300| 500| 900| 200| 900| 500| 91.2 (隠居じーさん) 2021/02/18(木) 15:13
追伸 当初から気にはなっていたのですが、小数点計算がメインの様ですが 手作業の分では合っています?。。。あまり気にしなくていいのかな? ^^; 私は、小数点扱いは、あまり、経験が有りませんので、計算誤差等に 関しては、お詳しい方のアドバイスをお待ちくださいね。 他のブック読込、とか、リスト作成等は、お手伝い出来るかもしれま せん。 また、年間情報なので、一月、二月位の単位で検索抜き 出しでしたら、そう気にしなくともよいかもしれませんが、半期とか 年間とかもお使いでしたら、処理速度も勘案され、月単位の平均集約 したものを12個用意すると、365ファイルアクセスを12アクセスに減ら すことが出来ます、また、コードの書き方でも多少スピードアップ可能 です。m(_ _)m (隠居じーさん) 2021/02/18(木) 15:38
隠居じーさん様 ご連絡ありがとうございます。
読み込みサンプルですが、流石です、ほぼ現物通りです。 細かいことですと、[読込ファイル]は特定のシステムから出力されているものなので、 1〜5行目が見出し固定されていたり、 A1セル内に、[印刷]のボタンがマクロで配置されていたりしますが、 開発に影響はないと思い、お伝えしておりませんでした。 大丈夫でしょうか?
これとこれって感じで指定する件ですが、 確かにそれだと間違いがないかと思うので、できればそうしたいところなんですが、 例えば1か月分だと、約31日分1つずつ指定するのは、 期間が長くなればなるほど手間かなと思っておりまして… 我儘なのは十分承知しておりますが、お力をお貸しいただけないかと。。。
小数点の件ですが、 列の項目によって、小数点が発生したりしなかったりまちまちです。 小数点も、小数点第一位までしか使われませんし、 切り捨て表示にはなっていないので、セルに入力されている数値をそのままaverage関数に使用できれば問題ないです。
また、月単位・半期・年間などのデータの作成は、 今回の件が解決しましたら、ご提案頂いたコードなどを基に、自分で作成してみようかと思っております。
○日から〇日迄まで とか、先週月〜日の などの集計が多いので、 処理速度は多少犠牲にしても、 365日間の好きな区間での集計が出来る機能はやはりマストかなと思っております。
どうか引き続きご助力くださいますようお願いいたします。 (おうぶ) 2021/02/18(木) 17:23
こんばんは ^^ 余計なお世話かもしれませんが。年跨ぎ時期はどう処理して おられるのでしょう。フォルダで切替でしょうか。可能でしたら フォルダ名に関わらずいつでも必要情報を取得するには、ファイル名 に年情報を持たせるのが良いのですが、諸般の事情もあり、変更不可 という事も、有るのでしょうね。(#^^#) 計算.xlsm の一行目のC〜D列[range("C1:D1")]は使用できます でしょうか。所用の為、今夜はこれにて失礼致します。m(_ _)m (隠居じーさん) 2021/02/18(木) 18:06
こんばんは ^^ とりあえず、作ってはみましたが、最小限の不測事態回避は組み込んだ つもりですが。本格的エラー処理、便利機能は何ら考慮されておりませ ん。← いい加減の代名詞。。。A^^;。。。なので、ご考察時の参考 程度にお止め下さいませ。 動作に必要な構成 1.計算.xlsmの Sheet1 を入力、表示兼用とし、何も入力無しない。【都度、初期化されます。】 2.計算.xlsmの Sheet2 A1以下に入力規則のリスト情報を入力済 3.計算.xlsmの Sheet2 C1以下に項目名情報を入力済 4.標準モジュールにコピペ分はモジュールのオブジェクト名を Module1とするか Thisworkbookモジュールの Call Module1.bKeisanMake(Me) のモジュール名を実際の物にご変更ください。 マクロ概略 入力は支店名、開始と終了年月日 支店名はA1、プルダウンリストにて入力 各期間はInputboxで8桁の整数、西暦yyyymmddで入力 開始 < 終了以外は処理中止 年度を跨ぐ処理はフォルダ訳にて対応 2021年は 2021 フォルダを作成後そちらに同じ形式[0101.xlsm]で保存 すれば可能だと思います。 入力規則のリスト、及び項目名に変更が有った場合は各Sheet2の情報を変更後 Private Sub Workbook_Open() を実行すれば反映されます。注意、項目列の増減は未対応で別途コード変更が 必要です。 バックアップ必須です。。。(# ^ ^ #) 不具合の修正はユーザー様のご通報まかせで ← どこかで 聞いたことがあるよぉな無いよぉな。。。 ( ̄▽ ̄;) うまくできてるかなぁ〜。。。どこかにダンプとってAverage関数でご 検算と、読込みもれ等の有無等綿密なるご検閲賜れば幸甚です。 修正等ございましたら、生きてれば現れますです。。。m(__)m 標準モジュールへ Option Explicit Sub OneInstanceMain(ByVal bNm As String) Const zProgramID As String = "計算.xlsm" Dim zTb As Workbook Dim sYmd As Double Dim eYmd As Double Dim v As Variant Dim t As Double t = Timer If bNm <> zProgramID Then Exit Sub If Application.Caller <> "BTN-START-PROCXXA1" Then MsgBox "規定のボタンより起動してください" Exit Sub End If Set zTb = Workbooks(zProgramID) If zTb.Worksheets("Sheet1").Cells(1) = "" Then Set zTb = Nothing MsgBox "支店名を入力して下さい" Exit Sub End If zTb.Worksheets("Sheet1").UsedRange.Offset(1).Clear aCceptYmd sYmd, eYmd If sYmd = 0 Or eYmd = 0 Then Set zTb = Nothing MsgBox "期間指定が不正です" Exit Sub End If v = myGetData(zTb, zTb.Worksheets("Sheet1").Cells(1).Value, sYmd, eYmd) If VarType(v) = vbBoolean Then Set zTb = Nothing MsgBox "フォルダかファイルの読込が失敗しました" Exit Sub End If wRiteData zTb, v Set zTb = Nothing If VarType(v) = (8192 + 12) Then Erase v MsgBox "終了 " & Format(Int(Timer - t) / 24 / 60 / 60, "hh : mm : ss") & _ Format((Timer - t) - Int(Timer - t), ".000") & " 秒" End Sub Private Sub aCceptYmd(ByRef arg1 As Double, ByRef arg2 As Double) Dim tmp(1) As Variant tmp(0) = Application.InputBox("yyyymmdd", "開始年月日", 20200101, , , , , 1) If tmp(0) = False Then Exit Sub tmp(1) = Application.InputBox("yyyymmdd", "終了年月日", 20200131, , , , , 1) If tmp(1) = False Then Exit Sub If tmp(1) < tmp(0) Then Exit Sub If TypeName(tmp(0)) = "Double" And Len(tmp(0)) = 8 Then arg1 = tmp(0) If TypeName(tmp(1)) = "Double" And Len(tmp(1)) = 8 Then arg2 = tmp(1) End Sub Private Function myGetData(ByVal tB As Workbook, _ ByVal ra1 As String, _ ByVal sYmd As Double, _ ByVal eYmd As Double) As Variant Dim fD As String Dim i As Double Dim j As Long Dim y As Long Dim n As Long Dim jD As Variant Dim s As Date Dim e As Date Dim var As Variant Dim v() As Variant Dim w() As Variant Dim iDx() As Variant Dim r As Range Dim wB As Workbook var = CStr(sYmd) s = DateSerial(CLng(Left(var, 4)), CLng(Mid(var, 5, 2)), CLng(Right(var, 2))) var = CStr(eYmd) e = DateSerial(CLng(Left(var, 4)), CLng(Mid(var, 5, 2)), CLng(Right(var, 2))) fD = tB.Path & "\" For i = s To e var = fD & CStr(Year(i)) If Dir(var, vbDirectory) <> "" Then If zFileExChk(var & "\" & Format(Month(i), "00") & Format(Day(i), "00") & ".xlsm") Then Set wB = Workbooks.Open(var & "\" & Format(Month(i), "00") & Format(Day(i), "00") & ".xlsm") With wB.Worksheets("Sheet1") y = .Cells(.Rows.Count, 1).End(xlUp).Row Set r = Intersect(.Range("A:AE"), .Range(.Rows(5), .Rows(y))) v = r.Value End With jD = Application.Match(ra1, r.Resize(, 1), 0) If IsError(jD) Then wB.Close Set wB = Nothing Set r = Nothing myGetData = False Exit Function End If ReDim w(1 To 30) For j = 1 To 30 w(j) = v(jD, j + 1) Next ReDim Preserve iDx(n) iDx(n) = w n = n + 1 Erase w wB.Close False Set wB = Nothing Else myGetData = False Erase iDx, v, w Exit Function End If If i Mod 15 = 0 And i >= 15 Then DoEvents Else myGetData = False Erase iDx, v, w Exit Function End If Next myGetData = iDx Erase iDx, v, w End Function Private Sub wRiteData(ByVal wB As Workbook, ByRef v As Variant) Dim i As Long Dim y As Long Dim x As Long Dim m() As Variant y = 5: x = 2: With wB.Worksheets("Sheet1") .UsedRange.Offset(1).ClearContents m = wB.Worksheets("Sheet2").Range("C1").CurrentRegion.Value .Cells(4, 1).Resize(, 31) = WorksheetFunction.Transpose(m) .Cells(4, 1) = Empty For i = 1 To 30 .Cells(y, x) = Round(WorksheetFunction.Average(Application.Index(v, 0, i)), 1) x = x + 1 Next End With Erase m End Sub Private Function zFileExChk(ByVal fNm As String) As Boolean If Dir(fNm) = "" Then zFileExChk = False Else zFileExChk = True End If End Function Sub bKeisanMake(ByVal wB As Workbook) Dim i As Long Dim r As Range Dim jD As Boolean Dim b As Object With wB.Worksheets("Sheet1") .UsedRange.ClearContents .Range("A1").NumberFormat = "@" With .Range("A1").Validation .Delete .Add Type:=xlValidateList, _ Formula1:="=Sheet2!" & Worksheets("Sheet2").Range("A1").CurrentRegion.Address .ShowError = True End With If .Buttons.Count <> 0 Then For Each b In .Buttons If b.Name = "BTN-START-PROCXXA1" Then jD = True Exit For End If Next End If If Not jD Then Set r = .Range("C1:D1") With .Buttons.Add(r.Left, r.Top, r.Width, r.Height) .OnAction = "'OneInstanceMain(""計算.xlsm"")'" .Characters.Text = "BTN" .Name = "BTN-START-PROCXXA1" End With End If jD = False End With End Sub
ここまで
ThisWorkbookモジュールへ Option Explicit Private Sub Workbook_Open() Call Module1.bKeisanMake(Me) End Sub (隠居じーさん) 2021/02/19(金) 17:25
追伸 上記コードでは保存終了致しましても 前回表示の情報は初期化の為消えます。 記録が必要な場合は別途、もう一工夫が 必要です。←w 便利機能のうちかも ^^; m(__)m 19:38 コード修正 Sub OneInstanceMain(ByVal bNm As String) の Stop を消しました (隠居じーさん) 2021/02/19(金) 18:37
隠居じーさん様 ご対応ありがとうございます。 仕事柄、休みが不定期のため、お返事できずすいません。
いただいたコード、実装して試してみようかと思います。 (おうぶ) 2021/02/20(土) 10:26
恐縮です。^^ 。。。バックアップ。。& 検算 。必須。。。です << _ _ >>
(隠居じーさん) 2021/02/20(土) 10:30
お世話になります。
m = wB.Worksheets("Sheet2").Range("C1").CurrentRegion.Value
の部分で多少詰まりましたが、無事導入できたので、 本件はこれで終了とさせていただきます。
皆さま、ご対応ありがとうございました。
(おうぶ) 2021/02/26(金) 13:10
Power Query勉強中の方向けです。 データの集約をPower Query で、集計(平均)はピボットテーブルで行います。
1)こんなテーブルを用意します。 テーブル名:期間設定
開始年月日 終了年月日 2019/1/1 2021/3/3 ←入力規則の日付で、開始が終了以下に設定
2)空のクエリを追加し、詳細エディターに以下をコピペ ★:実際のフォルダ、シートに修正すること
let //★ここは実際のフォルダを指定★ ソース = Folder.Files("D:\****\*****\****"), #"展開された Attributes" = Table.ExpandRecordColumn(ソース, "Attributes", {"Hidden"}, {"Hidden"}), 小文字テキスト = Table.TransformColumns(#"展開された Attributes",{{"Extension", Text.Lower, type text}}), フィルターされた行 = Table.SelectRows(小文字テキスト, each [Extension] = ".xlsm" and not Text.Contains([Name], "計算") and [Hidden] = false), 区切り記号の間に挿入されたテキスト = Table.AddColumn(フィルターされた行, "年", each Text.BetweenDelimiters([Folder Path], "\", "\", {0, RelativePosition.FromEnd}, {0, RelativePosition.FromEnd}), type text), 挿入された結合列 = Table.AddColumn(区切り記号の間に挿入されたテキスト, "年月日", each Text.Combine({[年], [Name]}, ""), type text), 抽出されるテキスト範囲 = Table.TransformColumns(挿入された結合列, {{"年月日", each Text.Middle(_, 0, 8), type text}}), 変更された型 = Table.TransformColumnTypes(抽出されるテキスト範囲,{{"年月日", type date}}), 期間設定 =Excel.CurrentWorkbook(){[Name="期間設定"]}[Content], 変更された型2 = Table.TransformColumnTypes(期間設定,{{"終了年月日", type date}, {"開始年月日", type date}}), 開始 = 変更された型2{0}[開始年月日], 終了 = 変更された型2{0}[終了年月日], フィルターされた行1 = Table.SelectRows(変更された型, each [年月日] >= 開始 and [年月日] <= 終了), 追加されたカスタム = Table.AddColumn(フィルターされた行1, "カスタム", each Excel.Workbook(File.Contents([Folder Path] & [Name]))), #"展開された カスタム" = Table.ExpandTableColumn(追加されたカスタム, "カスタム", {"Data", "Item"}, {"カスタム.Data", "カスタム.Item"}), //★ここは実際のシート名を指定★ フィルターされた行2 = Table.SelectRows(#"展開された カスタム", each [カスタム.Item] = "Sheet1"), 削除された他の列 = Table.SelectColumns(フィルターされた行2,{"年月日", "カスタム.Data"}), #"展開された カスタム.Data" = Table.ExpandTableColumn(削除された他の列, "カスタム.Data", {"Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9", "Column10", "Column11", "Column12", "Column13", "Column14", "Column15", "Column16", "Column17", "Column18", "Column19", "Column20", "Column21", "Column22", "Column23", "Column24", "Column25", "Column26", "Column27", "Column28", "Column29", "Column30", "Column31"}, {"Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9", "Column10", "Column11", "Column12", "Column13", "Column14", "Column15", "Column16", "Column17", "Column18", "Column19", "Column20", "Column21", "Column22", "Column23", "Column24", "Column25", "Column26", "Column27", "Column28", "Column29", "Column30", "Column31"}), //見出し部は、D列1〜4行が空白であることが前提 フィルターされた行3 = Table.SelectRows(#"展開された カスタム.Data", each [Column4] <> null and [Column4] <> ""), 昇格されたヘッダー数 = Table.PromoteHeaders(フィルターされた行3, [PromoteAllScalars=true]), 変更された型1 = Table.TransformColumnTypes(昇格されたヘッダー数,{{"月予算", type number}}), 削除されたエラー = Table.RemoveRowsWithErrors(変更された型1, {"月予算"}), 列名 = Table.ColumnNames(削除されたエラー), #"名前が変更された列 " = Table.RenameColumns(削除されたエラー,{{Table.ColumnNames(削除されたエラー){1}, "店舗名"}}) in #"名前が変更された列 "
3)閉じて次に読み込む 出力形式は、ピボットテーブルレポート 行フィールド;[店舗名] 値フィールド:必要な項目をすべて列挙 集計方法:平均
4)開始年月日、終了年月日を変更して、ピボットテーブルを右クリックし、「更新」を選択
(マナ) 2021/02/26(金) 19:43
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.