データヴィジュアライズのための 
簡単なWeb API開発まめ知識 
2014 Shizuoka.py #4
自己紹介 
♪ オーイシ(@oec014) 
♪ dogrun Inc. 
♪ 電子書籍(企画・デザイン等) 
現在は読書の補助&拡張ツールの開発… 
♪データマイニング、ビジュアライズなど
今回の資料は… 
• 特に、とにかく簡単にWeb APIを作ってみたい!とい 
う人向けです。 
• データセットを見てどのように実装できるか、デザイン 
できるかなど、作業を見通すためにも、普段サービス構 
築に関わらない人もAPIの理解を深めることがサービス 
構築にとても立つはず。 
• PythonのORマッパーつかうと簡単にできます。
つぶやきの音楽情報集めてます 
• Twitterで#nowplaying されたつぶやきを集めて、アーティスト、楽 
曲のランキングを作っています。(beatcaster.net) 
• 音楽そのものの価値に加えて、コンテンツへの言及やユーザーの関わ 
り方がコンテンツ波及の重要な要素になると考えています。ユーザー 
のコンテンツへの関わり(コンテクスト)の情報がさらにコンテン 
ツの消費を広げる要素になるような情報配信を模索しています。 
• TWでは、季節に関連して集中して聞かれる(つぶやかれる)曲を見 
かけます。「この日にこの曲がよく聞かれる」というような情報がコ 
ンテンツの波及に有効かも。
試しに「この日に聞かれる音楽」の 
Web APIを作ってみました 
• 日本語のつぶやきに限定して、1日に27000~2900程 
度の音楽がTwitterで#nowplayingされます。このつぶ 
やきを2年分集計しました。 
• つぶやきから機械的に曲名、アーティスト名抽出をして 
います。正規化していない…のでノイズ多めです。 
• 『つぶやきの量』&TFIDFで『特定の日の特定の曲のつ 
ぶやきの重みの指標』をJSONで返します。
musicinfo_count 
DataFrame 
特定日のTWをカウント 
NPs 
(ある日の総TW数) 
DataFrameで集計 
ある曲のTWが有った日 
をカウント 
Days 
(TWのあった日数) 
tfidfの値をupdate 
ただし10TW/day以 
上の曲に限る。 
musicinfo_all 
MySQL 
TW数をカウント 
musicinfo_count 
MySQL 
tfidf = (count/NPs) * log( 1/(Days/365) ) 
TFIDF集計プロセス
musicinfo_count 
day: VARCHAR 
count: INT 
song: VARCHAR 
artist: VARCHAR 
tfidf: FLOAT 
import_ja 
user: VARCHAR 
id: BIGINT 
day: VARCHAR 
txt: VARCHAR 
musicinfo 
song/artist 抽出: 
musicinfo_all 
user: VARCHAR 
date: DATE 
song: VARCHAR 
artist: VARCHAR 
txt: VARCHAR 
count: 
count 
1日に10件以上のデータのある 
song-artistの組み合わせのみ登録 
tfidf集計: 
get tfidf 
update 
category 
artist: VARCHAR 
category: VARCHAR 
API用データベースの概念モデル
df = pd.DataFrame(dic, columns=['id', 'count', 'tfidf']) 
plt.scatter(df['count'], df['tfidf']) 
plt.show() 
TW数 
TFIDF
APIサーバを簡単に作るために… 
ORマッパーを使います。 
• Webサービス構築にORMを使うとデータベースとの接 
続処理を専用のクラスに任せ、その処理を意識せずオブ 
ジェクトの追加や取り出しができるようになります。 
• 今回の例では自分が軽量フレームワークのFlaskとのセッ 
トでよく使っている”SQLAlchemyで”サンプルのサービ 
スを作っています。
モデルの定義とセッションの初期化 
テーブル定義とクラスマッピング 
from sqlalchemy import Table, Column, Integer, String, Float 
from sqlalchemy.ext.declarative import declarative_base 
Base = declarative_base() 
class Music(Base): 
__tablename__ = 'musicinfo_count' 
id = Column(Integer, primary_key=True) 
day = Column(String(12)) 
song = Column(String(100)) 
artist = Column(String(200)) 
count = Column(Integer) 
tfidf = Column(Float) 
def __init__(self, id, day, song, artist, count, tfidf): 
self.id = id 
self.day = day 
self.song = song 
self.artist = artist 
self.count = count 
self.tfidf = tfidf 
class ArtistCategory(Base): 
__tablename__ = 'artist_category' 
id =Column(Integer, primary_key=True) 
artist = Column(String(200)) 
category = Column(String(10)) 
def __init__(self, artist, category): 
self.artist = artist 
self.category = category モジュールを作りアプリケーション内で使いまわします。
モデルの定義とセッションの初期化 
セッションの作成 
##~いろいろ省略 
from sqlalchemy import create_engine, Date, and_, or_ 
from sqlalchemy.orm import sessionmaker, join 
from music import Music, Base, ArtistCategory 
from marshmallow import fields, Schema, Serializer 
import json 
##~いろいろ省略 
! 
Session = sessionmaker() 
engine = create_engine('mysql://root:xxx@localhost/xxx',encoding='utf-8)') 
Base.metadata.create_all(engine) 
Session.configure(bind=engine) 
session = Session() 
SQLAlchemy のSessionは「ORMとデータベースとの対話を全て担当して、データベース 
から読み出したり生成したマッピングインスタンスを保存しておく場所」とのこと。
ORMを使ったデータの取得 
単純なselect 
SQL では 
select * from Music; 
! 
SQLAlchemyでは 
result = session.query(Music).all() 
! 
※SQLAlchemyでは、Sessionのquery() メソッドを 
使ってQueryオブジェクトを生成します。
ORMを使ったデータの取得 
filterによる条件指定のパターン 
• 単純な問い合わせ。all()を実行することでDBに実際に問い合わせが発生する。 
session.query(Model).filter(Model.objectname == “hoo”).all() 
• カンマ区切りでAND条件を指定 
session.query(Model).filter(Model.object1==“hoo”, Model.object2==‘’bar” 
• filterメソッドの複数指定でAND条件 
session.query(Model).filter(Model.id==1).filter(Model.name==“hoo”).allI() 
• OR条件 
session.query(Model).filter(or_(Model.id==1, Model.name==“foo”).all() 
• ORとANDの組み合わせ 
session.query(Model).filter(or_(Model.id==1, Model.name==“hoo”), Model.value==1).all() 
• SQL文を指定 
session.execute(“SELECT * from musicinfo_count”) 
• LIKEによる部分一致 
session.querry(Model).filter(Model.name.like(‘%hoge%’).allI() 
• IN演算子 
session.query(Model).filter(Model.id.in_([1,2,3])).all() 
• BETWEEN演算子 
session.query(Model).filter(Model.value.between(1,3)
ORMを使ったデータの取得 
単純な条件指定でのデータ取得の例 
@app.route('/feature') 
def getRanking(): 
days = request.args.get("byday") 
if days: 
mu = session.query(Music).filter(Music.day.like('%'+ days +’%’))  
.order_by(Music.tfidf.desc()).all()
ORMを使ったデータの取得 
marshmallowによるデータのシリアライズ 
from marshmallow import fields, Schema, Serializer 
import json 
! 
##~諸々省略 
@app.route('/feature') 
def getRanking(): 
days = request.args.get("byday") 
if days: 
mu = session.query(Music).filter(Music.day.like('%'+ days +’%')). 
order_by(Music.tfidf.desc()).all() 
serialized = muserializer(mu, many=True).data 
return json.dumps(serialized, ensure_ascii=False).encode('utf-8') 
! 
class muserializer(Serializer): 
class Meta: 
fields = ("song", "artist","count", "tfidf")
musicinfo_count 
day: VARCHAR 
count: INT 
song: VARCHAR 
artist: VARCHAR 
tfidf: FLOAT 
import_ja 
user: VARCHAR 
id: BIGINT 
day: VARCHAR 
txt: VARCHAR 
musicinfo 
song/artist 抽出: 
musicinfo_all 
user: VARCHAR 
date: DATE 
song: VARCHAR 
artist: VARCHAR 
txt: VARCHAR 
count: 
count 
1日に10件以上のデータのある 
song-artistの組み合わせのみ登録 
tfidf集計: 
get tfidf 
update 
category 
artist: VARCHAR 
category: VARCHAR 
API用データベースの概念モデル
musicinfo_count 
marshmallow 
query 
category 
JSON生成のプロセス 
day: VARCHAR 
count: INT 
song: VARCHAR 
artist: VARCHAR 
tfidf: FLOAT 
SQLAlchemy 
filter() 
API (getRanking) 
song: 
artist: 
count: 
tfidf: 
category: 
json 
byday: %m-%d 
d: %d 
m: %m 
c:[idol, voice actor, その他] 
artist: VARCHAR 
category: VARCHAR
別テーブルの項目の追加 
内部結合を含む条件指定 
単純な内部結合はテーブルとカラムを指定し、 
filter()メソッドを複数AND条件で結合する。 
! 
session.query(ArtistCategory). 
filter(Music.artist==ArtistCategory.artist).filter(Music.artist==artist).all() 
! 
!
別テーブルの項目の追加 
queryのリスト化 
複合的な条件指定にクエリをリスト化する方法があります。 
検索条件が複雑になったときのフィルタを書きやすいかも。 
def getRanking2(): 
d = request.args.get("d") 
m = request.args.get("m") 
c = request.args.get("c") 
params = [] 
if d: 
md = m + "-" + d 
params.append(Music.artist==ArtistCategory.artist) 
params.append(Music.day==md) 
params.append(ArtistCategory.category==c) 
p = and_(*params) 
mu = session.query(Music).filter(p).all() 
serialized = mucatserializer(mu, many=True).data 
return json.dumps(serialized, ensure_ascii=False).encode('utf-8') 
filterの条件をリストに追加し 
and_メソッドで条件を結合した後 
リストをfilterメソッドに渡します。
別テーブルの項目の追加 
シリアライザで項目をJSONに追加する 
class mucatserializer(Serializer): 
category = fields.Method("get_category") 
def get_category(self, Music): 
ar = Music.artist 
return getcategory(ar) 
class Meta: 
fields = ("song", "artist", "count", "tfidf", "category") 
def getcategory(artist): 
name = session.query(ArtistCategory).filter(Music.artist==ArtistCategory.artist) 
.filter(Music.artist==artist).all() 
if name: 
return name[0].category 
else: 
return "" 
シリアライザ内でArtistCategoryテーブルの 
結合した項目を取得し追加ます。
別テーブルの項目の追加 
AND と ORの複合条件 
if ct: 
cts = ct.split(',') 
if len(cts) == 1: 
if "other" in ct: 
params.append(ArtistCategory.category=="") 
elif "VA" in ct: 
params.append(ArtistCategory.category=="VA") 
elif "IDOL" in ct: 
params.append(ArtistCategory.category=="IDOL") 
elif len(cts) == 2: 
param = (ArtistCategory.category==cts[0]) 
for item in cts: 
param = param | (ArtistCategory.category==item) 
params.append(param) 
md = m + "-" + d 
params.append(Music.artist==ArtistCategory.artist) 
params.append(Music.day==md) 
p = and_(*params) 
mu = session.query(Music).filter(p).all() 
serialized = mucatserializer(mu, many=True).data 
return json.dumps(serialized, ensure_ascii=False).encode('utf-8') 
OR条件でリストに追加 
AND条件でリストに追加
D3.js 
データをAPIをD3.jsを使って視覚化してみます
次回Shizuoka.pyがあるならやりたいこと… 
RDF Triplestore用のORMとして、SQLAlchemyと近いコードで記述でき 
る”RDFAlchemy”があるようです。 
! 
>>> c = Company.query.get_by(symbol = 'IBM') 
>>> print(c.companyName) 
International Business Machines Corp.

Shizuokapy4_データヴィジュアライズのための簡単なWeb API開発まめ知識

  • 1.
  • 2.
    自己紹介 ♪ オーイシ(@oec014) ♪ dogrun Inc. ♪ 電子書籍(企画・デザイン等) 現在は読書の補助&拡張ツールの開発… ♪データマイニング、ビジュアライズなど
  • 3.
    今回の資料は… • 特に、とにかく簡単にWebAPIを作ってみたい!とい う人向けです。 • データセットを見てどのように実装できるか、デザイン できるかなど、作業を見通すためにも、普段サービス構 築に関わらない人もAPIの理解を深めることがサービス 構築にとても立つはず。 • PythonのORマッパーつかうと簡単にできます。
  • 4.
    つぶやきの音楽情報集めてます • Twitterで#nowplayingされたつぶやきを集めて、アーティスト、楽 曲のランキングを作っています。(beatcaster.net) • 音楽そのものの価値に加えて、コンテンツへの言及やユーザーの関わ り方がコンテンツ波及の重要な要素になると考えています。ユーザー のコンテンツへの関わり(コンテクスト)の情報がさらにコンテン ツの消費を広げる要素になるような情報配信を模索しています。 • TWでは、季節に関連して集中して聞かれる(つぶやかれる)曲を見 かけます。「この日にこの曲がよく聞かれる」というような情報がコ ンテンツの波及に有効かも。
  • 5.
    試しに「この日に聞かれる音楽」の Web APIを作ってみました • 日本語のつぶやきに限定して、1日に27000~2900程 度の音楽がTwitterで#nowplayingされます。このつぶ やきを2年分集計しました。 • つぶやきから機械的に曲名、アーティスト名抽出をして います。正規化していない…のでノイズ多めです。 • 『つぶやきの量』&TFIDFで『特定の日の特定の曲のつ ぶやきの重みの指標』をJSONで返します。
  • 6.
    musicinfo_count DataFrame 特定日のTWをカウント NPs (ある日の総TW数) DataFrameで集計 ある曲のTWが有った日 をカウント Days (TWのあった日数) tfidfの値をupdate ただし10TW/day以 上の曲に限る。 musicinfo_all MySQL TW数をカウント musicinfo_count MySQL tfidf = (count/NPs) * log( 1/(Days/365) ) TFIDF集計プロセス
  • 7.
    musicinfo_count day: VARCHAR count: INT song: VARCHAR artist: VARCHAR tfidf: FLOAT import_ja user: VARCHAR id: BIGINT day: VARCHAR txt: VARCHAR musicinfo song/artist 抽出: musicinfo_all user: VARCHAR date: DATE song: VARCHAR artist: VARCHAR txt: VARCHAR count: count 1日に10件以上のデータのある song-artistの組み合わせのみ登録 tfidf集計: get tfidf update category artist: VARCHAR category: VARCHAR API用データベースの概念モデル
  • 8.
    df = pd.DataFrame(dic,columns=['id', 'count', 'tfidf']) plt.scatter(df['count'], df['tfidf']) plt.show() TW数 TFIDF
  • 9.
    APIサーバを簡単に作るために… ORマッパーを使います。 •Webサービス構築にORMを使うとデータベースとの接 続処理を専用のクラスに任せ、その処理を意識せずオブ ジェクトの追加や取り出しができるようになります。 • 今回の例では自分が軽量フレームワークのFlaskとのセッ トでよく使っている”SQLAlchemyで”サンプルのサービ スを作っています。
  • 10.
    モデルの定義とセッションの初期化 テーブル定義とクラスマッピング fromsqlalchemy import Table, Column, Integer, String, Float from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Music(Base): __tablename__ = 'musicinfo_count' id = Column(Integer, primary_key=True) day = Column(String(12)) song = Column(String(100)) artist = Column(String(200)) count = Column(Integer) tfidf = Column(Float) def __init__(self, id, day, song, artist, count, tfidf): self.id = id self.day = day self.song = song self.artist = artist self.count = count self.tfidf = tfidf class ArtistCategory(Base): __tablename__ = 'artist_category' id =Column(Integer, primary_key=True) artist = Column(String(200)) category = Column(String(10)) def __init__(self, artist, category): self.artist = artist self.category = category モジュールを作りアプリケーション内で使いまわします。
  • 11.
    モデルの定義とセッションの初期化 セッションの作成 ##~いろいろ省略 from sqlalchemy import create_engine, Date, and_, or_ from sqlalchemy.orm import sessionmaker, join from music import Music, Base, ArtistCategory from marshmallow import fields, Schema, Serializer import json ##~いろいろ省略 ! Session = sessionmaker() engine = create_engine('mysql://root:xxx@localhost/xxx',encoding='utf-8)') Base.metadata.create_all(engine) Session.configure(bind=engine) session = Session() SQLAlchemy のSessionは「ORMとデータベースとの対話を全て担当して、データベース から読み出したり生成したマッピングインスタンスを保存しておく場所」とのこと。
  • 12.
    ORMを使ったデータの取得 単純なselect SQLでは select * from Music; ! SQLAlchemyでは result = session.query(Music).all() ! ※SQLAlchemyでは、Sessionのquery() メソッドを 使ってQueryオブジェクトを生成します。
  • 13.
    ORMを使ったデータの取得 filterによる条件指定のパターン •単純な問い合わせ。all()を実行することでDBに実際に問い合わせが発生する。 session.query(Model).filter(Model.objectname == “hoo”).all() • カンマ区切りでAND条件を指定 session.query(Model).filter(Model.object1==“hoo”, Model.object2==‘’bar” • filterメソッドの複数指定でAND条件 session.query(Model).filter(Model.id==1).filter(Model.name==“hoo”).allI() • OR条件 session.query(Model).filter(or_(Model.id==1, Model.name==“foo”).all() • ORとANDの組み合わせ session.query(Model).filter(or_(Model.id==1, Model.name==“hoo”), Model.value==1).all() • SQL文を指定 session.execute(“SELECT * from musicinfo_count”) • LIKEによる部分一致 session.querry(Model).filter(Model.name.like(‘%hoge%’).allI() • IN演算子 session.query(Model).filter(Model.id.in_([1,2,3])).all() • BETWEEN演算子 session.query(Model).filter(Model.value.between(1,3)
  • 14.
    ORMを使ったデータの取得 単純な条件指定でのデータ取得の例 @app.route('/feature') def getRanking(): days = request.args.get("byday") if days: mu = session.query(Music).filter(Music.day.like('%'+ days +’%’)) .order_by(Music.tfidf.desc()).all()
  • 15.
    ORMを使ったデータの取得 marshmallowによるデータのシリアライズ frommarshmallow import fields, Schema, Serializer import json ! ##~諸々省略 @app.route('/feature') def getRanking(): days = request.args.get("byday") if days: mu = session.query(Music).filter(Music.day.like('%'+ days +’%')). order_by(Music.tfidf.desc()).all() serialized = muserializer(mu, many=True).data return json.dumps(serialized, ensure_ascii=False).encode('utf-8') ! class muserializer(Serializer): class Meta: fields = ("song", "artist","count", "tfidf")
  • 16.
    musicinfo_count day: VARCHAR count: INT song: VARCHAR artist: VARCHAR tfidf: FLOAT import_ja user: VARCHAR id: BIGINT day: VARCHAR txt: VARCHAR musicinfo song/artist 抽出: musicinfo_all user: VARCHAR date: DATE song: VARCHAR artist: VARCHAR txt: VARCHAR count: count 1日に10件以上のデータのある song-artistの組み合わせのみ登録 tfidf集計: get tfidf update category artist: VARCHAR category: VARCHAR API用データベースの概念モデル
  • 17.
    musicinfo_count marshmallow query category JSON生成のプロセス day: VARCHAR count: INT song: VARCHAR artist: VARCHAR tfidf: FLOAT SQLAlchemy filter() API (getRanking) song: artist: count: tfidf: category: json byday: %m-%d d: %d m: %m c:[idol, voice actor, その他] artist: VARCHAR category: VARCHAR
  • 18.
    別テーブルの項目の追加 内部結合を含む条件指定 単純な内部結合はテーブルとカラムを指定し、 filter()メソッドを複数AND条件で結合する。 ! session.query(ArtistCategory). filter(Music.artist==ArtistCategory.artist).filter(Music.artist==artist).all() ! !
  • 19.
    別テーブルの項目の追加 queryのリスト化 複合的な条件指定にクエリをリスト化する方法があります。 検索条件が複雑になったときのフィルタを書きやすいかも。 def getRanking2(): d = request.args.get("d") m = request.args.get("m") c = request.args.get("c") params = [] if d: md = m + "-" + d params.append(Music.artist==ArtistCategory.artist) params.append(Music.day==md) params.append(ArtistCategory.category==c) p = and_(*params) mu = session.query(Music).filter(p).all() serialized = mucatserializer(mu, many=True).data return json.dumps(serialized, ensure_ascii=False).encode('utf-8') filterの条件をリストに追加し and_メソッドで条件を結合した後 リストをfilterメソッドに渡します。
  • 20.
    別テーブルの項目の追加 シリアライザで項目をJSONに追加する classmucatserializer(Serializer): category = fields.Method("get_category") def get_category(self, Music): ar = Music.artist return getcategory(ar) class Meta: fields = ("song", "artist", "count", "tfidf", "category") def getcategory(artist): name = session.query(ArtistCategory).filter(Music.artist==ArtistCategory.artist) .filter(Music.artist==artist).all() if name: return name[0].category else: return "" シリアライザ内でArtistCategoryテーブルの 結合した項目を取得し追加ます。
  • 21.
    別テーブルの項目の追加 AND とORの複合条件 if ct: cts = ct.split(',') if len(cts) == 1: if "other" in ct: params.append(ArtistCategory.category=="") elif "VA" in ct: params.append(ArtistCategory.category=="VA") elif "IDOL" in ct: params.append(ArtistCategory.category=="IDOL") elif len(cts) == 2: param = (ArtistCategory.category==cts[0]) for item in cts: param = param | (ArtistCategory.category==item) params.append(param) md = m + "-" + d params.append(Music.artist==ArtistCategory.artist) params.append(Music.day==md) p = and_(*params) mu = session.query(Music).filter(p).all() serialized = mucatserializer(mu, many=True).data return json.dumps(serialized, ensure_ascii=False).encode('utf-8') OR条件でリストに追加 AND条件でリストに追加
  • 22.
  • 23.
    次回Shizuoka.pyがあるならやりたいこと… RDF Triplestore用のORMとして、SQLAlchemyと近いコードで記述でき る”RDFAlchemy”があるようです。 ! >>> c = Company.query.get_by(symbol = 'IBM') >>> print(c.companyName) International Business Machines Corp.