最も必要スキルを少なく・・・だと何とかして1行に加工してそこからピボットテーブルが簡単なのですが。
作業に反復性もありそうなことなので、なるだけ手間がかからないようにとVBAで組み立ててみました。
以下のコードをマクロに書き込んで見てください。
'=====プログラムここから=====
Public Sub CountNameNum()
Dim objRange As Range, objSrcRange As Range, objOutputRange As Range
Dim objDicNameList As Object
Dim OutputArray() As Variant, CountArray As Variant
Dim StartRowCount As Long, EndRowCount As Long
Dim StartColCount As Long, EndColCount As Long
Dim NowIndex As Long, MaxIndex As Long
'***********ここから設定記述部分です***********
'元のデータが入っている場所の範囲を記入
' (列の幅とデータの始まりの行さえ合っていれば終了行は自動調整します)
Set objSrcRange = Sheets("シート名を記入").Range("A:U") '見出し+20列のため例はA~Uにしてあります
'結果を表示させたい場所の左上を記入(別のシートでもOK)
Set objOutputRange = Sheets("シート名を記入").Range("A1")
'***********ここからプログラム本体です***********
'今回のミソ、Dictionaryオブジェクトの宣言
Set objDicNameList = CreateObject("Scripting.Dictionary")
'念のため全消去(なくてもOK)
objDicNameList.RemoveAll
'範囲の終了行の調整
StartColCount = objSrcRange.Column + 1 '左端の列は見出しのため、わざと左端1列削る
EndColCount = objSrcRange.Column + objSrcRange.Columns.Count - 1
StartRowCount = objSrcRange.Row + 1 '1行目は見出し行のため、わざと先頭1行を削る
EndRowCount = objSrcRange.Cells(1).End(xlDown).Row '最終行は自動判定で取得(見出し列の最終行にあわせられます)
'修正された範囲でデータ範囲を再定義
Set objSrcRange = objSrcRange.Worksheet.Range(objSrcRange.Worksheet.Cells(StartRowCount, StartColCount), objSrcRange.Worksheet.Cells(EndRowCount, EndColCount))
'再定義された範囲から名前を集計開始
Application.StatusBar = "現在、セルの情報をカウント中です"
For Each objRange In objSrcRange
If objRange.Value <> "" Then 'セルの中身が空白の場合はカウントアップしない
If Not objDicNameList.Exists(objRange.Value) Then
'今まで出たことのない名前が見つかった場合は名前を登録する
objDicNameList.Add objRange.Value, Array(objRange.Value, 0)
End If
'見つかった人の名前のカウントを+1する
' (上で追加しているので、ここの部分でリストに名前がないことはありえない)
objDicNameList.Item(objRange.Value) = Array(objDicNameList.Item(objRange.Value)(0), objDicNameList.Item(objRange.Value)(1) + 1)
End If
Next
Application.StatusBar = "現在、カウントした結果を出力中です"
MaxIndex = objDicNameList.Count
ReDim OutputArray(1 To MaxIndex + 1, 1 To 2) '見出しのためMaxIndexは1行多くしてます
OutputArray(1, 1) = "名前"
OutputArray(1, 2) = "回数"
'集計した結果を取り出し
CountArray = objDicNameList.Items
For NowIndex = 0 To MaxIndex - 1
OutputArray(NowIndex + 2, 1) = CountArray(NowIndex)(0)
OutputArray(NowIndex + 2, 2) = CountArray(NowIndex)(1)
Next
'結果をExcelの指定セルへ転記
With objOutputRange.Worksheet.Range(objOutputRange, objOutputRange.Offset(UBound(OutputArray, 1) - 1, UBound(OutputArray, 2) - 1))
.Value = OutputArray
'ついでに回数の多い順に並べ替え
.Sort "回数", xlDescending, , , , , , xlYes
End With
'出したものはお方付け
Application.StatusBar = False
Set objRange = Nothing
Set objSrcRange = Nothing
Set objOutputRange = Nothing
Set objDicNameList = Nothing
End Sub
'=====プログラムここまで=====
設定部分のシート名とセルの列範囲をいじってあげてください。
私のPCで実行すると、20列×10000行にびっしりランダムに名前を入力した場合(およそ17000人分)、処理に大体7秒かかりました。理論上は、シートに収まれば何行に増えても何列に増えても問題ないと思います。(行数は自動で修正されますが、列数を変更した場合は設定を修正してください)
VBAのほうは、コメントは多めに入れましたが、やや難しい内容になっています。詳しく知りたい場合は、
[vba dictionary]のあたりで検索をするといいでしょう。参考になるURLを2つほど紹介しておきます。
http://www.eurus.dti.ne.jp/~yoneyama/Excel/vba/vba_dictionary.html
http://officetanaka.net/excel/vba/tips/tips80.htm
どちらの内容も同じですが、その内容を、セルの範囲が増えてもいいように等、汎用性を持たせているため、上のプログラムは難しい記述になっています。基本・原理は全く同じです。
最後に、名前順に並べ替えたい場合は、[ソートする]の部分の1行を
.Sort "名前", , , , , , , xlYes
に書き換えてください。
お礼
回答ありがとうございます。 #3さんのVBAと迷いましたが、 反復性のある作業であると汲みとっていただけて、 より汎用性の高いスクリプトをご提示いただけたので、 こちらをBAとさせていただきました。 回答を寄せてくださった皆様、ありがとうございました!