shizuokapy4_データヴィジュアライズのための簡単なweb api開発まめ知識
TRANSCRIPT
データヴィジュアライズのための簡単な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_countDataFrame
特定日のTWをカウント
NPs(ある日の総TW数)
Days(TWのあった日数)
ある曲のTWが有った日をカウント
tfidfの値をupdate
ただし10TW/day以上の曲に限る。
musicinfo_allMySQL
TW数をカウント
musicinfo_countMySQL
tfidf = (count/NPs) * log( 1/(Days/365) )
DataFrameで集計
TFIDF集計プロセス
day: VARCHARcount: INTsong: VARCHARartist: VARCHARtfidf: FLOAT
musicinfo_count
user: VARCHARdate: DATEsong: VARCHARartist: VARCHARtxt: VARCHAR
musicinfo_all
user: VARCHARid: BIGINTday: VARCHARtxt: VARCHAR
import_ja
song/artist 抽出:
musicinfo
count:
count
tfidf集計:
get tfidf
1日に10件以上のデータのあるsong-artistの組み合わせのみ登録
update
artist: VARCHARcategory: VARCHAR
category
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")
day: VARCHARcount: INTsong: VARCHARartist: VARCHARtfidf: FLOAT
musicinfo_count
user: VARCHARdate: DATEsong: VARCHARartist: VARCHARtxt: VARCHAR
musicinfo_all
user: VARCHARid: BIGINTday: VARCHARtxt: VARCHAR
import_ja
song/artist 抽出:
musicinfo
count:
count
tfidf集計:
get tfidf
1日に10件以上のデータのあるsong-artistの組み合わせのみ登録
update
artist: VARCHARcategory: VARCHAR
category
API用データベースの概念モデル
JSON生成のプロセス
SQLAlchemy
day: VARCHARcount: INTsong: VARCHARartist: VARCHARtfidf: FLOAT
musicinfo_count
filter()
API (getRanking)song:artist:count:tfidf:category:
json
byday: %m-%dd: %dm: %mc:[idol, voice actor, その他]
query
marshmallow
artist: VARCHARcategory: VARCHAR
category
別テーブルの項目の追加内部結合を含む条件指定
単純な内部結合はテーブルとカラムを指定し、 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.