コンサルでデータサイエンティスト

仕事でPythonを書いてます。機械学習、Webマーケティングに興味があります。趣味は旅です。

データサイエンティストが競技プログラミングをやるべき理由

前回の記事からだいぶ時間があいてしまいましたが、平成最後ということでひとつ書かせてください。


はじめに
先日登壇した勉強会で「データサイエンティストはエンジニアリングスキルを高めるべき」という趣旨の主張を展開した上で、「エンジニアリングスキルを高めるために競技プログラミングをやっている」と言ったところ、参加者から以下の質問を頂きました。


競技プログラミングと実務のエンジニアリングはどのように関係があるのですか?」


本記事では上記の疑問に対する自分なりの考えをまとめていこうと思います。
アルゴリズム重視や可読性の低いコードばかりで実務に役立たないと思われがちな競技プログラミングですが、実はいろんな側面で業務に役立つことを学ぶことができます。
今回の記事はエンジニア全般を対象とした話をするので、データサイエンティストでない方にも読んでいただきたい内容になっています。


以下に当てはまる読者には特に刺さるかもしれません

  • 周りと比べて仕事が遅い気がする人
  • なんとなく勢いで進めた結果、手戻りしがちな人
  • 問題・仕様の理解に時間がかかる人


目次

競技プログラミングとは

競技プログラミングとは、プログラミングを使って速く・正確に・多くの問題を解き、その結果を競う競技です。現在、世界中でオンラインコンテストが実施されています。
有名なところだと Topcoder, Codeforces, Google Code Jam などがあります。


とりわけ日本で競技プログラミングの話がされる場合は、Atcoderというオンラインコンテストサイトが連想されることが多いです。Atcoderは日本語で問題が出題されることが特徴で、毎週末開催されるコンテストには約5000人がエントリーしているほど有名になってきました (2019/04時点)。


f:id:hktech:20190429120236p:plain
Atcoder Beginner Contest 124 より


ほとんどの競技プログラミングサイトではオンラインジャッジという仕組みが採用されています。これは出題された問題に対するソースコードを提出すると、問いの条件を満たすようなテストケースを自動でチェックしてくれるというものです。提出コードがすべてのテストケースを満たしていて、実行時間も制限時間内だったとき初めてその問題をクリアしたことになります。

オンラインコンテストでは100~120分という制限時間の中で、複数の問題を世界中の人と同時に解き、その結果を競います。

競技プログラミングで身につくスキル

競技プログラミングで身につくスキルは、開発の各フェーズで必要とされるスキルにかなり近いです。まずは開発業務にどのようなフェーズがあるのか確認していきます。

開発を行う時のフェーズはおおよそ以下のようになります。

f:id:hktech:20190429014202p:plain

これをベースに、競プロでどのようなスキルが身につくのかをみていきましょう。

仕様の理解

問題や仕様の理解はコンテストにおいては最も重要なポイントです。問題の理解を誤ったまま先に進んでしまうと、すべて最初からやり直しとなってしまいます。これでは素早くたくさんの問題を解くことができません。仕事で炎上するときもだいたい認識の齟齬が原因ですよね。

問題読解能力は国語力でもあり、業務を行う上でもとても大切なスキルです。競技プログラミングを始めると、問題文から瞬時にイメージを正確にとらえるスキルを身につけることができます。

例えば次のような問題があります。

f:id:hktech:20190429133831p:plain

f:id:hktech:20190429133918p:plain
Atcoder Beginner Contest 125 より


上級者であれば瞬時に理解して実装に落とし込めるようですが、私は凡人なのでまず紙でイメージを書き出します。

この問題では隣り合ってる数字のペアの正負を何回でも操作して入れ替えられるよということなので、それを書き出すとこんな感じになります。

f:id:hktech:20190429135815j:plain


全ての数字を正にすることができました。入出力例と合っているのでこの解釈で間違いなさそうです。

もうひとつ例を書くと

f:id:hktech:20190429140022j:plain


今度は全ての数字を正にすることはできず、1つだけ負の数字が残っています。

このように問題を理解し、イメージをアウトプットすることが大切です。チーム開発などでは認識齟齬が致命傷になることが多いですが、実際の業務でも仕様を理解してイメージをチームメンバーと共有することが大事だと思います。競技プログラミングを始めてから、このような作業が速く・正確に行えるようになった気がします。


実装設計

問題の理解ができたら、次は実装方針の設計を行います。特にデータサイエンティストの人はこの設計をせずにコードを書き出してしまう場合が多い*1ですが、開発では設計を必ず行いましょう。もっとも競技プログラミングで上位を目指すのであれば設計をしている暇なんてありませんが…慣れるまでは急がば回れで堅実にやっていきましょう。


「仕様の理解」パートで、この問題設定では以下のパターンがあることがなんとなくわかりました。

  1. すべての数字を正にできるパターン
  2. 1つだけ負の数字が残ってしまうパターン

ここで、いくつか入力例を書き出してみます。

-10 5 -4
→ 10 5 4
→ 19

10 -4 -8 -11 3
→ 10 4 8 11 -3
→ 30

10 -5 2 8
→ 10 5 -2 8
→ 21

9 -4 3 -1 5
→ 9 4 3 1 5
→ 22

ここまで書くと、以下のことがわかります。

  1. 負の数字が奇数個のとき ⇒ 1つだけ負の数字が残ってしまう
  2. 負の数字が偶数個のとき ⇒ すべての数字を正にできる

つまり、以下のような設計方針を立てることができます。

  • 負の数字をカウントする
  • 負の数字カウントが偶数なら、入力数列の絶対値の和をとる
  • 負の数字カウントが奇数なら、入力数列の絶対値の和から最小数の絶対値 * 2 を引く(既に足した分も引くため)

また、コンテストでは実行時間制限が設けられているため、おおまかな計算量を試算しておく必要があります。頑張ってコードを書いて回した結果、計算時間オーバーではもったいないです。

今回の方針は、入力列(長さN)に対して1回のループを回すためO(n)で解けそうだということがわかります。

テスト設計

前段でテストパターンを洗い出したものの、これで全パターンを満たせているでしょうか?

競技プログラミングでは全てのテストケースを通過しなければ、正解とはなりません。さらに、Atcoderではミスをおかす度にペナルティが付与されてしまうため、テストケースに漏れがないかよく確認したいところです。

実装する前にテスト設計を行う理由としては、テストパターンに漏れがあった場合に実装方針に影響が出てしまうからです。手戻りは未然に防ぎたいですよね。

先の例で、入力パターンをいくつか用意して設計を行い、これを実際のテストパターンとして使う開発手法はテスト駆動開発と呼ばれていたりします。テスト設計と実装設計を同時にやってしまうようなものなのですが、テストパターンが完璧に押さえられている場合は、これを満たすようにロジックを実装するだけなのでとても楽で確実です。


前章で列挙した入力パターンを確認すると、どうやら0が入るパターンを考慮できていなかったようです。このパターンでは、負の数字カウントが奇数なのにもかかわらず全ての数字を正にすることができています。

f:id:hktech:20190429145013j:plain

テストケース漏れ??と思いましたが、そもそも0が入る場合は最後に引くものがないため問題なさそうです。

負の数字カウントが奇数なら、入力数列の絶対値の和から最小数の絶対値 * 2 を引く(既に足した分も引くため)

符号が変わるポイントは忘れがちなので、与えられる数字の範囲に注意してテストケースを設計したいです。念のため新たな入力例もテストパターンに加えて準備万端です。あとは、これらのテストケースが全て通るようなコードを書くだけです。

テスト駆動開発がいかに有用であるかということを、競技プログラミングを通して肌で理解することができます。

実装・テスト

多くのオンラインコンテストでは、好きなプログラミング言語を選んで実装することができます。高速で実行ができるという点で、C++を使っている方が多いですが、実務で使っている好きな言語で書けば良いと思います。

実装方針はすでに立っているので、実装をしていきましょう。
以下が私がPythonで書いたコードです。

# read input
n = int(input())
x_list = [int(i) for i in input().split()] 
 
minus_count = 0
abs_list = []
for x in x_list:
  if x < 0: # x is minus
    minus_count += 1
  abs_list.append(abs(x)) # take abs
    
if minus_count % 2 == 0: # minus_count is even
  print(sum(abs_list))
else: # minus_count is odd
  print(sum(abs_list) - 2 * min(abs_list))


競技プログラミングはプログラムを書くことがメインだと思われがちですが、実は実装に入るまでにほとんどのことは終わっています。上記のコードはプログラミング初心者でもぐぐったら書けるレベルですし、初心者向けの問題であれば実装の高度なテクニックは求められません。

他にも、Atcoderではいろいろな人のコードをみることができます。例えば以下の例は、同じ方針を短いコードでまとめあげています。

n=int(input());a=list(map(int,input().split()));c=0
for i in range(n):
    if a[i]<1:c+=1
    a[i]=abs(a[i])
a.sort()
print(sum(a) if c%2==0 else sum(a)-2*a[0])

最後に、用意した全てのテストパターンを通過することを確認しコードを提出します!

まとめ

競技プログラミングで得たスキルと実務の開発がどのように関係しているかということについて紹介しました。

仕様理解、設計、実装、テストという流れは一般的な開発フローと似ていますし、アルゴリズムや実装テクニックといった部分以外にも実務に役立つことをたくさん学ぶことができます。実装に入るまでにほとんどのことが終わっていると書きましたが、実務でもこのように仕様理解と設計をしっかりと行うことが早く仕事をこなす近道だと思います。急がば回れです。

今回はアルゴリズム以外の部分に着目しましたが、今後はアルゴリズムや計算量を意識した実装に関する記事を書ければいいなと思います。

*1:Jupyter notebook の副作用でしょうか…

Scalaで配列/リストを操作するSeqコレクションについてまとめた

Scalaで配列またはリストを使う際はSeqがおすすめです。本記事では、他のプログラミング言語における配列やリストとは少し異なる挙動を示すSeqコレクションの文法および操作についてまとめました。

Seqの宣言

ScalaのSeqは以下のように作ることができます。

scala> Seq("One", "Two", "Three")
res0: Seq[String] = List(One, Two, Three)


変数seqにリストを持たせる場合は以下のように宣言することができます

scala> val seq: Seq[String] = Seq("One", "Two", "Three")
seq: Seq[String] = List(One, Two, Three)


Seqの要素についてString型をあらかじめ宣言する必要がある点が、Pythonなどのスクリプト言語との大きな違いです。

Seqの要素にアクセスする

ScalaでSeqの要素にアクセスする方法について紹介します。

次のように添字1を指定して要素を取り出すことができます。

scala> seq(1)
res1: String = Two


Scalaの添字は他の多くのプログラミング言語と同じく0で始まるため、2番目の要素が抽出されていることがわかります。

また、Seqの先頭要素や末尾要素へのアクセスはheadやlastを使って次のように行うことができます。

scala> seq.head
res2: String = One

scala> seq.last
res3: String = Three


Seqのinitやtailを使うと、次のように末尾以外・先頭以外の要素を取り出すことができます。

scala> seq.init
res4: Seq[String] = List(Two, Three)

scala> seq.tail
res5: Seq[String] = List(One, Two)

Seqの要素を追加または削除する

ScalaでSeqに要素を追加または削除する方法について紹介します。

先頭に要素を追加するには+: 、末尾に追加するには :+ と記述します。

scala> "Zero" +: seq
res6: Seq[String] = List(Zero, One, Two, Three)

scala> seq +: "Four"
res7: Seq[String] = List(One, Two, Three, Four)


このとき、元のseqは変化していないことに気づかれた方もいると思います。Scalaのコレクションは基本的に非破壊的であるため、新しいオブジェクトが返されるということを覚えておきましょう。

複数のSeqを連結する

Scalaで複数のSeqを結合する方法は次の通りです。

scala> val seqToAdd = Seq("Hello", "World")
seqToAdd: Seq[String] = List(Hello, World)

scala> seq ++ seqToAdd
res8: Seq[String] = List(One, Two, Three, Hello, World)


Seq同士が連結されていることが確認できます。

Seqの要素をソートする

ScalaでSeqをソートするにはsortedを使います。また、逆順に表示するにはreverseを使います。この辺りはpythonと同じメソッド名ですね。

scala> Seq(8, 3, 4, 1).sorted
res9: Seq[Int] = List(1, 3, 4, 8)

scala> Seq(8, 3, 4, 1).reverse
res10: Seq[Int] = List(1, 4, 3, 8)

まとめ

ScalaのSeqコレクションの文法および操作についてまとめました。本記事で取り上げた内容はかなり基本的なメソッドばかりですが、Scalaのリスト操作にはmap, filter, foldLeft, foldRight などの特徴的なメソッドがまだまだあります。これらについては別記事で紹介できればと思います。

Scalaでパッケージをインポートする

Scalaでパッケージおよびライブラリをインポート(import) する方法についてご紹介します。
Scalaに入門したばかりの私が、次の入門書を参考にしながらまとめました。

実践Scala入門

実践Scala入門



パッケージのインポート

Scalaでは、Javaと同様にクラス、オブジェクト、トレイトなどは必ず何らかのパッケージに所属します。パッケージのインポートは他のプログラミング言語と同じく、プログラムの冒頭で行われます。
ドメイン名を逆順にしたものをパッケージ名とするのが一般的な慣習です。

例えば、SparkのWindow関数を使うためのクラスをインポートする方法は以下の通りです。

import org.apache.spark.sql.expressions.Window

この例では、Windowという名前で org.apache.spark.sql.expressions.Window を参照することができるようになります。


ここで、Windowという同じ名前のクラスがインポートされていて衝突してしまった場合にはどのようにすればいいでしょうか?
Scalaではインポート時の名前衝突を以下のように防ぐことができます。

import org.apache.spark.sql.expressions.{Window => SparkWindow}


また、特定のクラスだけでなく、あるパッケージ配下に所属するすべてのクラスやオブジェクトをインポートしたいときには以下のようなインポート文を書くことができます。

import org.apache.spark.sql.expressions._

このようなインポートはワイルドカードインポートなどと呼ばれます。

まとめ

Scalaでパッケージをインポートする方法についてまとめました。Javaに馴染みがある方にとっては特に違和感のない内容となっていますが、他の言語から始められた方はこの機会にインポートの仕方を確認してみてください。

【Scala入門】 ScalaでFizzBuzz問題を解く

Apache Sparkの学習の一環としてScalaに触れてみることにしました。
本記事では、Scalaの基本的な構文を確認した後にFizzBuzz問題を解いていきます。

SparkとScalaの関係性について詳しく知りたい方はこちら
hktech.hatenablog.com


FizzBuzz問題とは

FizzBuzz問題とは1から100までの数字について、3で割り切れる場合に”Fizz”、5で割り切れる場合に”Buzz”、両者で割り切れる場合に”FizzBuzz”、それ以外の場合はその数字を出力させるプログラミング問題です。

調べてみたらもともとは英語圏で遊ばれている言葉遊びらしいです。

プレイヤーは円状に座る。最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」(Bizz Buzzの場合は「Bizz」)、5で割り切れる場合は「Buzz」、両者で割り切れる場合(すなわち15で割り切れる場合)は「Fizz Buzz」(Bizz Buzzの場合は「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

ja.wikipedia.org


いや、ただの宴会ゲーム。難しそう。


Scalaのプログラミング構文

いきなりScalaの実装に入る前に、基本的なプログラミング構文を確認しておきます。

FizzBuzz問題で必要となるのは大きく3つです。

  • 1から100までの数字のリスト(配列)
  • リストをまわすためのfor文
  • 条件分岐するためのif-else文

リスト

まずはリストの構文について確認していきます。

1から3までのリストを作ります。

$ List(1, 2, 3)
 res: List[Int] = List(3, 1, 4)


直感的ですね。てかScalaって型推論あるのか、、、確かに型宣言はしてないですよね。

次に1から100のリストを作ります。さすがに1から100まですべて書き出すわけにはいかないですよね。
そこで、Rangeというものを使います。これはPythonと同じですね。

$ Range(1, 101)
 res: scala.collection.immutable.Range = Range 1 until 101


できてそうですね。これをリスト型にします。

$ Range(1, 101).toList
 res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)


こんな書き方もできます。

$ (1 to 100).toList
 res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)

for文

Scalaにおけるfor文の書き方についてみていきます。

こんな感じです。簡単です。

$ for (s <- Array("a", "b", "c")) println(s)
 a
 b
 c



それでは前のセクションで学んだリストを使い、1から100を出力してみましょう。

$ for (s <- (1 to 100)) println(s)
 1
 2
 3
 4
 ..........
 
 99
 100


できました。
あとは条件分岐にしたがって、”Fizz” や “Buzz” を出力するようにしていきましょう。

if-else文 / match文

Scalaにおけるif-else条件分岐の書き方について確認していきます。

if-else文は次のように書きます。

$ val i = 3
$ if (i % 2 == 0) print("even") else print("odd")
 odd

また、他の言語のswitch文にあたる構文として、Scalaにはmatch文があります。

var num = 1
num match{
    case 1 => println("one")
    case 2 => println("two)
    case _ => println("other")
}


ScalaFizzBuzz問題を解く

FizzBuzz問題に必要なScalaプログラミング構文を完全に理解したところで、早速実装をしてみました。

for (s <- (1 to 100))
    if (s % 15 == 0)
        println("FizzBuzz")
    else if (s % 3 == 0)
        println("Fizz")
    else if (s % 5 == 0)
        println("Buzz")
    else
        println(s)

無事にFizzBuzz問題が解けました!
コーディング面接でFizzBuzzを書く機会があれば間違いなくScalaで書くでしょう。

もっといい実装方法があるか
こちらの記事では、関数をつくって次のように実装しています。

def fizzBuzz(i: Int): String = {
    val fizz = i % 3 == 0
    val buzz = i % 5 == 0
 
    if (fizz && buzz) "FizzBuzz"
    else if (fizz) "Fizz"
    else if (buzz) "Buzz"
    else i.toString()
    }
   
for (i <- 1 to 99) println(fizzBuzz(i))


さらにScalaっぽく書くパターンとして、次のように処理をチェーンするような書き方があります。

def fizzBuzz(i: Int) = {
  1 to i
} map {
  case i if i % 15 == 0 => "FizzBuzz"
  case i if i % 3 == 0 => "Fizz"
  case i if i % 5 == 0 => "Buzz"
  case _ => i
} foreach {
  s => println(s)
}

fizzBuzz(100)

まずリストを作った上で、その要素をmapで加工し、最後に出力の処理を行います。


最後はmatch文を用いた実装です。こちらが最もシンプルでわかりやすいですね。

def fizzBuzz(i: Int) = i match {
  case i if i % 15 == 0 => "FizzBuzz"
  case i if i % 3 == 0 => "Fizz"
  case i if i % 5 == 0 => "Buzz"
  case _ => i.toString()
}



まとめ

ScalaFizzBuzz問題にチャレンジしてみました。Scalaの基本的なプログラミング構文を活用し、さまざまな実装パターンについて確認しました。Scalaは複雑なプログラミング言語であるというイメージがありましたが、意外にシンプルに書くことができました。引き続きScalaに関する記事を書いていきたいと思います。

Apache Spark: PythonとScalaのどっちを使うべきか比較する

データサイエンスプロジェクトで Spark を使う場合、必ず議論に上がるのがPythonScalaのどちらのプログラミング言語を採用すべきかということです。

Sparkは元来Scalaで書かれているため、Scalaで処理コードを書いていくのが直感的にも自然なことです。しかし、データサイエンティストの多くはPythonに親しみがあるため、Sparkから取得したデータに対してPythonで書いた機械学習モデルや処理コードをそのまま適用したいということがあります。

そこで、近年台頭してきたのがPySparkです。PySparkとは、PythonApache Sparkを実行するためのAPIです。PySparkの発展により、PythonによるSparkの扱いやすさは劇的に改善してきており、ScalaPythonのどちらを使うべきかという論争は大きな盛り上がりをみせています。

本記事では、PythonScalaのどちらでSparkを扱うべきかということについて、それぞれのメリットとデメリットを紹介しながら比較していきます。

apache_spark


Apache Spark とは

どのプログラミング言語を扱うべきかという比較に入る前に、Apache Sparkの概要について確認していきましょう。

Apache Sparkとは大量のデータに対して高速に分散処理を行うOSSフレームワークです。APIとしてはPython, Java, Scala, R などのプログラミング言語が用意されています。大規模データを扱う分散アプリケーションを開発する際にはSparkの利用が必ず検討されるといってもよいでしょう。

類似の分散処理フレームワークとしてHadoopがありますが、HadoopJavaで書かれているのに対して、SparkはScalaで書かれています。また、HadoopとSparkの大きな違いとして、HadoopではMapReduceにおける入出力のたびにストレージにアクセスする必要があったのに対して、Sparkではデータをメモリに保存することで高速化を図っています。これはインメモリ処理と呼ばれ、機械学習などのように入出力処理が頻繁に発生するようなアプリケーションでは実行性能が100倍程度改善することもあります。

www.graffe.jp


Apache SparkではSpark MLという機械学習ライブラリが備わっているのも特徴です。これによって、ユーザは複雑な分散処理を考えることなく、高速で動作する機械学習手法を実装することが可能です。

Python vs Scala

Sparkを扱う際に、PythonScalaのどちらを選択すればよいのでしょうか。それぞれのメリットとデメリットを比較していきます。

実行速度

まずは、PythonScalaを実行速度の観点で比較します。

平均的に、ScalaPythonより約10倍の速さで実行することが可能です。Scalaは実行時にJava Virtual Machine (JVM) を使用するのに対して、Pythonは動的型変換を伴うインタプリタ言語であるため、その実行速度の差は歴然です。さらにSparkのライブラリはすべてScalaで書かれているため、Pythonでこれらを使用しようとすれば、Scalaそのものでライブラリを扱うよりは実行速度が遅くなることは明らかです。

したがって、実行速度の観点ではScalaが圧倒的に優位です。

扱いやすさ

次にPythonScalaを扱いやすさの観点で比較していきます。

まずは、実装のしやすさという点では、Pythonに圧倒的な軍配が上がります。ご存知の通り、Pythonは学習コストが低いプログラミング言語のひとつとして知られており、データを扱う多くのエンジニアやデータサイエンティストはPythonに慣れているということがあります。一方で、ScalaをマスターするためにはJavaの基本的な理解が必要であるため、情報系出身者ではない多くのデータサイエンティストがここで脱落します。さらに厄介なことに、Javaに習熟しているものでもScalaの特殊な構文に慣れるには時間がかかるといわれています。このようなことから、チーム全体の実装スピード運用コストという点でもPythonのほうが優れているといえるでしょう。

Scalaの実装が簡単ではないということを説明しましたが、Scalaを扱えることができればSparkのフレームワークをより簡単に活用することができます。SparkのライブラリはScalaAPIコレクションを活用しているため、これを理解しておけば内部的な動作を把握することができるほか、用途に応じた修正をすることが可能です。また、動的型変換を行うPythonと比較すると、Scalaは静的に型が定義されるため、コンパイル時にエラーを発見することができるという点で安全性が高いです。

結論

実行速度では、ScalaのほうがPythonより早く、扱いやすさという点では、Pythonのほうがやや優勢であるということがわかりました。

結局、PythonScalaのどちらを使えばよいのでしょうか?

例えば、大規模データを処理してレポーティングをしたいという場合にはPythonが有効です。商用システムでない場合、実行速度は大きな問題ではないため、すばやく実装でき、可視化ライブラリなどが充実しているPythonを選ぶのがおすすめです。特に、機械学習系の案件であれば、データサイエンティストの多くはPythonに慣れているはずなので、こちらを使うのが無難でしょう。

商用のシステムでSparkを活用したい場合、PythonScalaのどちらを選ぶかは会社の形態やリソース状況によって変わると思います。

事業会社で、大規模データを扱うシステムまたはサービスを長期的に開発する場合はScalaがよいでしょう。学習コストがかかってしまうものの、Sparkを本質的に理解するためにはScalaの理解は欠かせないほか、パフォーマンス観点や最新のSparkライブラリにアクセスしやすさからもScalaを選択することが賢明です。

一方で、コンサルや短納期での実装が求められるような会社においてはPythonを選択するのがよいでしょう。特に、受注をするような場合は保守がしやすく、実装できる人材が多いプログラミング言語を選択することが一般的であり、Sparkにおいてもこれは当てまります。


まとめ

Apache Sparkを扱う際にPythonScala のどっちを使うべきかということについてまとめました。実行速度や扱いやすさの観点から、それぞれのメリットとデメリットがあることがわかりました。

Pythonコモディティ化してきている部分があるので個人的にはScalaに挑戦してみるのもよいかと思います。

コンサルでデータサイエンティストとして働く

コンサルティングファーム(会社)におけるデータサイエンティストの求人が近年増加しています。かつては経営戦略などに力を入れていた外資コンサルティングファームや、大規模なシステム開発などに強みがあった IT 系コンサルティング企業も、デジタル領域でのデータを活用したビジネスに大きな期待を寄せています。本記事ではコンサルタントとデータサイエンティストの役割の違いについて触れながら

  • コンサルでデータサイエンティストとして働くために必要なスキル
  • データサイエンティストが求められている理由

ということについて、実際にコンサルでデータサイエンティストとして働いている立場からご紹介します。コンサルにデータサイエンティストとして入社したい就活中の学生や、転職を考えている方にとって少しでも参考になれば幸いです。


目次

データサイエンティストとは

データサイエンティストの定義は個人や企業によって解釈が様々となっています。専門職としてのデータサイエンティストの役割は、統計学または機械学習 ×プログラミングによる課題解決をすることであると考えています。


Excelでピボットテーブルを組んでクロス集計をしたり、BIツールでユーザーの行動を解析するといった仕事もデータアナリティクスの重要な仕事ではあるものの、これらはコンサルティングファームコンサルタントであれば順当に持っているスキルであるため、このような仕事を専門に行う職種は本記事におけるデータサイエンティストのスコープ外とします。


data_scientist

データサイエンティストの分類

データサイエンティストとは大きくデータアナリスト機械学習エンジニアの2種類の職種に分けて考えることができます。

machine_learning_engineer

データサイエンティストと機械学習エンジニアは別物として解釈する場合もありますが、コンサルでははっきりと区別されていないことが多いです。そもそもコンサルティングファームにおいては、データアナリストの方が機械学習エンジニアよりもはるかに数が多いというのがその理由であるともいえます。しかし、人工知能を画像認識システムやチャットボットのサービスなどに実装していくような案件が増えつつある昨今のコンサル界隈では、後者の機械学習エンジニアへの需要も高まっています。ここでは、データアナリストと機械学習エンジニアの違いについて説明していきます。


データアナリストとは、データ分析で得た示唆を通してビジネス上の意思決定を支援する職種です。彼らはクライアントの業務やサービスを深く理解し、クライアントと密にコミュニケーションをしながら分析を企画、設計、実行していきます。場合によっては、データ分析で得た示唆を施策に落とし込んでいくサポート役を担うこともあります。

機械学習エンジニアとは、サービスやシステムに機械学習を用いた機能を実装していく職種です。彼らは機械学習モデルを商用のサービスやシステムに耐えうるレベルで実装を行います。人工知能やAIと言われるようなシステムの開発は彼らが担っていると考えてよいでしょう。


これらの2つの職種は、重複があるものの求められているスキルが微妙に異なります。データサイエンティストに必要なスキルにはどういったものがあるのでしょうか。


データサイエンティストに必要なスキル

データサイエンティストに必要なスキルは、一般的に以下の3軸で考えることができます。

  1. 機械学習・統計
  2. ビジネス(コンサルティングスキル・コミュニケーション力)
  3. エンジニアリング(プログラミング力・インフラの知識)


data_scientist_skill


優れたデータサイエンティストはこの三角形に示されたスキルを一定レベルで満たしていることが条件となります。機械学習・統計はデータアナリスト及び機械学習エンジニアの両方に必要な必須スキルであるものの、データアナリストは左側のビジネス寄り機械学習エンジニアは右側のエンジニアリング寄りといったように、多少のスキルの偏りがあることが一般的です。


機械学習・統計のスキル

機械学習・統計のスキルとは、データを適切に解釈し、様々な機械学習や統計的手法のなかから問題解決に結びつくものを取捨選択し、実装・分析を進めていく力です。こちらはデータサイエンティストとして当然持っておくべきスキルであり、これが欠けている場合は他のエンジニアやコンサルタントとの違いが無いと言えるでしょう。基本的な数学の知識から、統計学機械学習に関する知識に加え、最新の論文を読解するリサーチ力や特定の分野(自然言語処理、画像認識、異常検知、時系列データなど)に関する理解など、多くが求められています。しかし、データ分析のみで価値を生み出すことは年々難しくなっているため、近年では機械学習や統計のスキルが突き抜けているからといって、一流のデータサイエンティストを名乗れるような状況ではなくなってきています。

② エンジニアリングスキル

エンジニアリングスキルとは、プログラミングやサーバ・データベースなどのインフラに関する知識をはじめとしたスキルを指します。イメージとしては、情報工学科の授業で学ぶような内容に加え、実務で使用するWebフレームワークや、AWSGCPなどのクラウドサービス、ビッグデータを扱うソフトウェア(Spark, Hadoop, Hive)に関する知識が求められています。

③ ビジネススキル

ビジネススキルとは、データ分析を通して顧客に価値を提供するスキルを指します。事業会社であれば自社サービス、コンサルであればクライアントのサービスやドメインについて深く理解した上で、社内外とコミュニケーションをとりながらデータアナリティクスプロジェクトを円滑に進めることが求められています。また、データ分析を完璧に理解していないような方に対して、分析の方針や結果をわかりやすく説明するといった能力も必要です。




コンサルでデータサイエンティストとして働く場合、高いビジネススキルは当然のように求められることが多いです。機械学習・統計やエンジニアリングのスキルについては、関わる案件によって求められるレベルに大きな幅があります。案件ごとに技術を素早くキャッチアップできるような高い学習意欲や、手戻りが発生しないように分析を設計できる力が重要視されるといってよいでしょう。


コンサルでデータサイエンティストが求められる理由

データサイエンティストという職種は、幅広いスキルセットと深い知見が求められるということがわかりました。このような役割が、なぜいまコンサルティングファームで求められているのでしょうか。
コンサルでデータサイエンティストが必要とされている理由について考えていきます。


コンサルタントとは従来よりクライアントの課題に対して、深い知見と高いスキルによって解決策を提示し、変革を推し進めるという役割を担っています。コンサルにおけるデータサイエンティストはデータ分析という強力な武器をもったコンサルタントです。

データ分析が課題解決のひとつの手段として確立した背景には、冒頭でご紹介した通り、国内・国外のコンサルティングファームの売上の多くが経営・戦略コンサルティング事業から、デジタルやITといったドメインでのソリューション提供といった事業にシフトしているということがあります。そのような状況のなかで、昨今のデータ分析や人工知能ブームが火をつける形で、データ分析や機械学習システムの開発に関連する案件が急激に増えています。例えば、従来ではExcelなどで行っていた要因分析は、PythonやRによる多変量分析に取って代わられたり、多くのコンサルティングファームが得意としていたオペレーションの自動化は、機械学習を含むアルゴリズムによってより高度化されています。

また、より上流の意思決定に携わるコンサルティングにおいてもデータという武器は極めて有効です。クライアントの役員になにかを提案しようとするとき、その業界に関する知識では到底勝つことができません。しかし、データによる示唆を説明すれば新たな発見を与えることができます。客観的な情報であるデータとクライアントのドメイン知識を繋ぐ役割として、データサイエンティストが必要不可欠な存在となっています。

データサイエンティストとしてコンサルに就職する

コンサルティングファームでいかにデータサイエンティストが求められているかということが伝わったかと思います。読者の方の中には、データサイエンティストとして就職先を探している学生の方や、転職先の候補としてコンサルでデータサイエンティストを視野に入れている方もいるでしょう。


データサイエンティストは多くのスキルや知識を必要とする職種です。しかし、就職や転職をするタイミングですべてのスキルが必要であるというわけではありません。多くの業界でデータサイエンティストが不足している状況にあり、未経験に近い形で採用される方も会社によっては一定数いると聞きます。コンサルティングファームでは、多くの場合にプロジェクトにいきなりアサインされることが多いため、最低限のスキルとして以下は身につけておきたいです。


私自身は paiza というオンライン学習サイトを中心に python を学びました。現在では、UdemyAidemyといったデータ分析や機械学習プログラミングに特化したオンラインコースを提供するサイトも増えてきているので、一通りやってみるというのもよいかもしれません。上記スキルに加えて、学習意欲が最も大切です。これがあればコンサルティングファームでもやっていけるでしょう。

データサイエンティストとしてコンサルで働くメリット

データサイエンティストは多くの会社で求められていますが、コンサルでデータサイエンティストとして働くメリットとして、多様な業界のデータ分析プロジェクトに触れることができるということがあります。また、プロジェクトベースで動くため、自らが立ち上げメンバーとなってより実践的なスキルが身につけることができるという利点もあります。

一方で、多くのコンサルティングファームでは分析だけでプロジェクトが終わってしまい、実装に入れないケースも多々あります。先にも述べた通り、まだまだ機械学習のエンジニアリングが強くない会社も多く、開発は外注していることも多々あります。

したがって、現時点でデータサイエンティストとして機械学習エンジニア寄りのスキルを伸ばしていきたい方に、コンサルでデータサイエンティスト職に就くことは強くおすすめできません。そのような方は、ウェブ系のサービスを持ったメガベンチャーで働くほうが、エンジニアリングスキルを成長させていくことができるように思います。(あくまで、個人的な意見です)


まとめ

コンサルティングファームでデータサイエンティストとして働く立場から、データサイエンティストという職種の役割や、データサイエンティストが求められている背景についてご紹介しました。データサイエンスに必要なスキルは多岐にわたりますが、コンサルティングファームではプロジェクトごとに必要なスキルを身につけていくのが一般的です。本記事を読んで、データサイエンティストという職種に興味を持っていただける方がいれば幸いです。

pandasのTimestampで決まった範囲の時刻リストを作る

Python で日時関連のデータ操作をするときにdatetime モジュールを使用されている方も多いと思いますが、pandasを使ったTimestamp関連の操作が便利なので個人的にはおすすめです。本記事は時刻を含むデータを処理する際に、決まった範囲の時刻リストを作りたいというときに見返すためのメモ記事となります。

時刻リストとは次のようなイメージです。

['2018-12-19 08:00:00+00:00',
 '2018-12-19 08:01:00+00:00',
 '2018-12-19 08:02:00+00:00',
 '2018-12-19 08:03:00+00:00',
 '2018-12-19 08:04:00+00:00']


ある時刻範囲のループをまわして、なにかしらの処理をしたい場合などにご活用ください。

現在の時刻を取得

Python での実装をご紹介します。
まず、次のように現在の時刻を取得します。

$ time = pd.Timestamp.now(tz='GMT').floor(freq='T')



このとき、用途にあわせて時刻のタイムゾーンを指定することができます。

  • 世界標準時(GMT)で設定したい場合: tz='GMT'
  • 日本時刻で設定したい場合: tz=’Asia/Tokyo’

とします。

また .floor を指定することで細かい時刻の端数を丸めることができます。
ここでは、freq=’T’ としているので分単位に丸めています。(ちなみに H が1時間単位、Dが1日単位になります)

このように、GMTタイムゾーンで現在時刻を取得することができました。

$ time
Timestamp('2018-12-19 11:02:00+0000', tz='GMT')


時刻リストの取得

時刻リストを取得します。
今回は、現在の時刻の2分前から、現在の時刻の2分後までの時刻リストを取得したい場合を考えます。

ここではpandasの date_rangeメソッドを使って配列を作っていきます。

$ time_range = pd.date_range(pd.Timestamp(time, tz='GMT') - pd.offsets.Minute(2), pd.Timestamp(time, tz='GMT') + pd.offsets.Minute(2), freq='T')


pandasの date_rangeでは、開始時刻、終了時刻、周期を引数に指定します。
今回は分単位のリストがほしいので freq=’T’ としています。

また時刻の指定においては、pd.offsets を使用して時間単位で分数を足したり引いたりしています。datetimeモジュールなどと比較すると、かなり直感的に時刻を加算・減算することができます。

ちなみにdatetimeモジュールでは下記のように記述する必要がありました。

$ from dateutil.relativedelta import relativedelta
$ dt + relativedelta(minutes=1)



実際に取得したリストをみてみましょう。

$ time_range
 ['2018-12-19 11:00:00+00:00',
  '2018-12-19 11:01:00+00:00',
  '2018-12-19 11:02:00+00:00',
  '2018-12-19 11:03:00+00:00',
  '2018-12-19 11:04:00+00:00']


現在時刻の11:02から前後2分の時刻リストが取得できていることが確認できました。

Timestamp型から UNIX time に変換

pandas の Timestamp型データは簡単にUNIX timeにも変換することができます。
取得したリストを一気に変換したい場合は、リスト内包表記を使って次のように変換可能です。

$ [t.value // 1000000 for t in time_range]
   [1545217200000,
    1545217260000,
    1545217320000,
    1545217380000,
    1545217440000]


まとめ

pandas の Timestamp で時刻リストを作る方法について簡単に紹介しました。特定の時刻範囲のループをまわす処理を書きたいときには、本記事でご紹介したpandasのTimestampを利用してみてはいかがでしょうか。