ためすう

pandas で データ型を確認する

2019-06-29

やったこと

pandas のデータ構造である

  • Series
  • DataFrame

について調べたことを書きます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
print(pd.__version__)
0.20.3

調査

DataFrame の各列の型を確認してみます。

data = {'state': ['Ohio', 'Nevada'],
       'year': [2000, 2001]}
df = DataFrame(data)
df.dtypes

出力結果

state    object
year      int64
dtype: object

参考

第12章 唯一を保証する: Singleton

2019-06-29

「Rubyによるデザインパターン」の読書メモです。

ただ1つのインスタンスしか持てないクラスで、その1つのインスタンスへのアクセスをグローバルに提供します。

シングルトンを使わないロガーの例

test.rb

class SimpleLogger
  attr_accessor :level

  ERROR = 1
  WARNING = 2
  INFO = 3

  def initialize
    @level = WARNING
  end

  def error(msg)
    p "#{self.class}##{__method__}"
    p msg
  end

  def warning(msg)
    p "#{self.class}##{__method__}" if @level >= WARNING
    p msg
  end

  def info(msg)
    p "#{self.class}##{__method__}" if @level >= INFO
    p msg
  end
end

logger = SimpleLogger.new
logger.level = SimpleLogger::INFO

logger.info('1つめ')
logger.info('2つめ')

logger2 = SimpleLogger.new

p '---'
p logger.object_id
p logger2.object_id

出力結果

$ ruby test.rb
"SimpleLogger#info"
"1つめ"
"SimpleLogger#info"
"2つめ"
"---"
70311664013160
70311664012640

シングルトンを適用したロガーの例

test.rb

class SimpleLogger
  attr_accessor :level

  @@instance = SimpleLogger.new

  ERROR = 1
  WARNING = 2
  INFO = 3

  def initialize
    @level = WARNING
  end

  def error(msg)
    p "#{self.class}##{__method__}"
    p msg
  end

  def warning(msg)
    p "#{self.class}##{__method__}" if @level >= WARNING
    p msg
  end

  def info(msg)
    p "#{self.class}##{__method__}" if @level >= INFO
    p msg
  end

  def self.instance
    return @@instance
  end

  private_class_method :new
end

logger = SimpleLogger.instance
logger.level = SimpleLogger::INFO

logger.info('1つめ')
logger.info('2つめ')

logger2 = SimpleLogger.instance

p '---'
p logger.object_id
p logger2.object_id

出力結果

$ ruby test.rb
"SimpleLogger#info"
"1つめ"
"SimpleLogger#info"
"2つめ"
"---"
70234052873820
70234052873820

シングルトンモジュール

Singleton モジュールを使って、実装することができます。

include Singleton

Ruby でシングルトンを使ってみる – ためすう

シングルトンとしてのクラス

class ClassBasedLogger
  ERROR = 1
  WARNING = 2
  INFO = 3

  def self.error(msg)
    p "#{self.class}##{__method__}"
    p msg
  end
end

シングルトンとしてのモジュール (自作)

module ClassBasedLogger
  ERROR = 1
  WARNING = 2
  INFO = 3

  def self.error(msg)
    p "#{self.class}##{__method__}"
    p msg
  end
end

モジュールは、インスタンス化するためにあるのではないと少しは示すことができます。

Need-to-Know原則に基づくシングルトン

Need-to-Know原則とは > 「必要な人にだけ情報を開示する」という情報セキュリティにおける基本的な考え方です。

require 'singleton'

class DatabaseConnectionManager
  include Singleton

  def get_connection
    p "#{self.class}##{__method__}"
  end
end

class PreferenceManager
  def initialize
    @reader = PrefReader.new
    @writer = PrefWriter.new

    @preferences = {
      :display_splash => false,
      :background_color => :blue
    }
  end

  def save_preferences
    @writer.write(DatabaseConnectionManager.instance, @preferences)
  end

  def get_preferences
    @preferences = @reader.read(DatabaseConnectionManager.instance)
  end
end

class PrefWriter
  def write(manager, preferences)
    p "#{self.class}##{__method__}"
    p preferences
    manager.get_connection
  end
end

class PrefReader
  def read(manager)
    p "#{self.class}##{__method__}"
    manager.get_connection
    p '読み取ったもの'
  end
end

PreferenceManager.new.get_preferences
PreferenceManager.new.save_preferences

シングルトンは実装詳細なので、シングルトンであると知っているクラスを少なくした方が良いようです。

第11章 オブジェクトを改良する: Decorator

2019-06-27

「Rubyによるデザインパターン」の読書メモです。

Decoratorパターンについて

既存のオブジェクトに対して簡単に機能を追加するためのパターンです。Decoratorパターンを使うとレイヤ状に機能を積み重ねていくことができ、それぞれの状況でまさに必要なだけの機能をもつオブジェクトを作ることができます。

今回、ファイルに下記の形式で書き出すことができるクラスを作ります。

  • プレーンテキスト
  • チェックサム付き
  • タイムスタンプ付き
  • 行番号付き

継承ベースのアプローチ

継承ベースのアプローチでは、あり得るすべての機能の組み合わせを前もって設計時に考えなければならない,ということです。

クラスの数が増えすぎるので、不採用とします。

Decoratorパターンで実装した例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

class WriterDecorator
  def initialize(real_writer)
    @real_writer = real_writer
  end

  def write_line(line)
    @real_writer.write_line(line)
  end

  def pos
    @real_writer.pos
  end

  def rewind
    @real_writer.rewind
  end

  def close
    @real_writer.close
  end
end

class NumberingWriter < WriterDecorator
  def initialize(real_writer)
    super(real_writer)
    @line_number = 1
  end

  def write_line(line)
    @real_writer.write_line("#{@line_number}: #{line}")
    @line_number += 1
  end
end

class CheckSummingWriter < WriterDecorator
  attr_reader :check_sum

  def initialize(real_writer)
    @real_writer = real_writer
    @check_sum = 0
  end

  def write_line(line)
    line.each_byte {|byte| @check_sum = (@check_sum + byte) % 256}
     # check_sum の計算...
    @real_writer.write_line("#{line} (checksum: #{@check_sum})")
  end
end

class TimeStampingWriter < WriterDecorator
  def write_line(line)
    @real_writer.write_line("#{Time.new}: #{line}")
  end
end

writer = CheckSummingWriter.new(
  TimeStampingWriter.new(
    NumberingWriter.new(SimpleWriter.new('final.txt'))
  )
)
writer.write_line('Hello out there')

出力された final.txt

1: 2019-06-23 13:28:36 +0900: Hello out there (checksum: 164)

Forwardable を使った例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

出力された final2.txt

2019-06-23 13:34:25 +0900: Hello out there

ラッピングする例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

w = SimpleWriter.new('final3.txt')

class << w
  alias old_write_line write_line

  def write_line(line)
    old_write_line("#{Time.new}: #{line}")
  end
end

w.write_line('write_line')
w.old_write_line('old_write_line')

出力された final3.txt

2019-06-23 13:42:53 +0900: write_line
old_write_line

モジュールを使った例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

module TimeStampingWriter
  def write_line(line)
    super("#{Time.new}: #{line}")
  end
end

w = SimpleWriter.new('final4.txt')
w.extend(TimeStampingWriter)
w.write_line('hello')

出力された final4.txt

2019-06-23 13:45:02 +0900: hello

第10章 オブジェクトに代理を立てる: Proxy part2

2019-06-27

「Rubyによるデザインパターン」の読書メモです。

Proxy で重複を取り除いたパターン

test.rb

class BankAccount
  attr_reader :balance

  def initialize(starting_balance=0)
    @balance = starting_balance
  end

  def deposit(amount)
    @balance += amount
  end

  def withdraw(amount)
    @balance -= amount
  end
end

class AccountProxy
  def initialize(real_account)
    @subject = real_account
  end

  def method_missing(name, *args)
    p "Delegating #{name} message to subject."
    @subject.send(name, *args)
  end
end

ap = AccountProxy.new(BankAccount.new(100))
ap.deposit(25)
ap.withdraw(50)
p ap.balance

出力結果

$ ruby test.rb
"Delegating deposit message to subject."
"Delegating withdraw message to subject."
"Delegating balance message to subject."
75

method_missing をオーバーライドして、委譲メソッドを別々に定義しなくても良くなりました。

Proxy パターンの注意

method_missingの使いすぎは、継承の使いすぎと同様、あなたのコードをわかりにくくしてしまう代表的な原因になるということです。

次に来る実装者には、実装により意図が伝わるようにしてください。

pandas の head、tail を使ってみる

2019-06-26

やったこと

pandas で DataFrame のデータを確認してみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
print(pd.__version__)
0.20.3

調査

csv ファイルを読み込みます。

train = pd.read_csv("./train.csv")

head を使ってみる

デフォルトだと先頭から5行出力されます。

train.head()

tail を使ってみる

デフォルトだと最終行から5行出力されます。

train.tail()

参考

第10章 オブジェクトに代理を立てる: Proxy part1

2019-06-26

「Rubyによるデザインパターン」の読書メモです。

Proxy パターンは下記の問題の解決策となります。

  • オブジェクトへのアクセス制御
  • 場所に依存しないオブジェクトの取得方法の提供
  • オブジェクト生成の遅延

プロキシーが気にするのは、何かをすることが許されているのは誰で、許されていないのが誰なのかということです。

Proxy パターンの例

3つの問題について、解決させるように試みたコードです。

test.rb

class BankAccount
  attr_reader :balance

  def initialize(starting_balance=0)
    @balance = starting_balance
  end

  def deposit(amount)
    @balance += amount
  end

  def withdraw(amount)
    @balance -= amount
  end
end

require 'etc'
class BankAccountProxy
  def initialize(starting_balance, owner_name, &creation_block)
    @starting_balance = starting_balance
    @owner_name = owner_name
    @creation_block = creation_block
  end

  def balance
    check_access
    subject.balance
  end

  def deposit(amount)
    check_access
    subject.deposit(amount)
  end

  def withdraw(amount)
    check_access
    subject.withdraw(amount)
  end

  def check_access
    if Etc.getlogin != @owner_name
      raise "Illegal access: #{Etc.getlogin} cannot access account."
    end
  end

  def subject
    @subject || (@subject = @creation_block.call)
  end
end

account = BankAccount.new(100)
account.deposit(50)
account.withdraw(10)
p account.balance

account2 = BankAccountProxy.new(account.balance, 'hogehoge') { BankAccount.new(300) }
account2.deposit(50)
account2.withdraw(10)
p account2.balance

出力結果

$ ruby test.rb
140
340

第9章 ギャップを埋める: Adapter

2019-06-26

「Rubyによるデザインパターン」の読書メモです。

アダプタは既存のインターフェースと必要なインターフェースとの間の深い溝を橋渡しするオブジェクトです。

Adapter パターンの例

例えば、TextObject を表示する実装が既にあって、

フォーマットが異なる BritishTextObject に手を入れることができない場合にも

BritishTextObject を扱うことができます。

class Renderer
  def render(text_object)
    text = text_object.text
    size = text_object.size_inches
    color = text_object.color

    p text
    p size
    p color
  end
end

class TextObject
  attr_reader :text, :size_inches, :color

  def initialize(text, size_inches, color)
    @text = text
    @size_inches = size_inches
    @color = color
  end
end

class BritishTextObject
  attr_reader :string, :size_mm, :colour

  def initialize(string, size_mm, colour)
    @string = string
    @size_mm = size_mm
    @colour = colour
  end
end

class BritshTextObjectAdapter < TextObject
  def initialize(bto)
    @bto = bto
  end

  def text
    @bto.string
  end

  def size_inches
    @bto.size_mm / 4
  end

  def color
    @bto.colour
  end
end

t = TextObject.new('text2', 100, 'blue')
Renderer.new.render(t)

b = BritishTextObject.new('string', 300, 'green')
bto = BritshTextObjectAdapter.new(b)
Renderer.new.render(bto)

Ruby でクラス拡張するパターン

class Renderer
  def render(text_object)
    text = text_object.text
    size = text_object.size_inches
    color = text_object.color

    p text
    p size
    p color
  end
end

class TextObject
  attr_reader :text, :size_inches, :color

  def initialize(text, size_inches, color)
    @text = text
    @size_inches = size_inches
    @color = color
  end
end

class BritishTextObject
  attr_reader :string, :size_mm, :colour

  def initialize(string, size_mm, colour)
    @string = string
    @size_mm = size_mm
    @colour = colour
  end
end

# 上記 BritishTextObject は require されているつもり
# 拡張
class BritishTextObject
  def text
    string
  end

  def size_inches
    size_mm / 4
  end

  def color
    colour
  end
end

t = TextObject.new('text2', 100, 'blue')
Renderer.new.render(t)

b = BritishTextObject.new('string', 300, 'green')
Renderer.new.render(b)

まとめ

繰り返しますが、コードはパターンにとってそれほど重要ではないということを覚えておいてください。意図が重要です。不適切なインターフェースを持つオブジェクトに困っていて、不適切なインターフェースを扱う痛みがシステム中に広がることを防ぎたい場合に限り、アダプタを選んでください。

第8章 命令を実行する: Command

2019-06-25

「Rubyによるデザインパターン」の読書メモです。

コマンドをクラスにするパターン

class SlickButton
  attr_accessor :command

  def initialize(command)
    @command = command
  end

  def on_button_push
    @command.execute if @command
  end
end

class SaveCommand
  def execute
    p "#{self.class}##{__method__}"
  end
end

save_button = SlickButton.new(SaveCommand.new)
save_button.on_button_push

コマンドをコードブロックにするパターン

コマンドは何か特定のことを行うコードの単純なラッパーで、唯一の存在理由は、適切なタイミングでコードを実行することです。

class SlickButton
  attr_accessor :command

  def initialize(&block)
    @command = block
  end

  def on_button_push
    @command.call if @command
  end
end

new_button = SlickButton.new do
  p "#{self.class}"
end

new_button.on_button_push

記録 + 戻るの例

class Command
  attr_reader :description

  def initialize(description)
    @description = description
  end

  def execute
  end

  def unexecute
  end
end

class CreateFile < Command
  def initialize(path, contents)
    super("Create file: #{path}")
    @path = path
    @contents = contents
  end

  def execute
    p "#{self.class}##{__method__}"
  end

  def unexecute
    p "#{self.class}##{__method__}"
  end
end

class DeleteFile < Command
  def initialize(path)
    super("Delete file: #{path}")
    @path = path
  end

  def execute
    p "#{self.class}##{__method__}"
  end

  def unexecute
    p "#{self.class}##{__method__}"
  end
end

class CopyFile < Command
  def initialize(source, target)
    super("Copy file: #{source} to #{target}")
    @source = source
    @target = target
  end

  def execute
    p "#{self.class}##{__method__}"
  end

  def unexecute
    p "#{self.class}##{__method__}"
  end
end

class CompositeCommand < Command
  def initialize
    @commands = []
  end

  def add_command(cmd)
    @commands << cmd
  end

  def execute
    @commands.each {|cmd| cmd.execute}
  end

  def unexecute
    @commands.reverse.each {|cmd| cmd.unexecute}
  end

  def description
    description = ''
    @commands.each {|cmd| description += cmd.description + "\n"}
    description
  end
end

cmds = CompositeCommand.new
cmds.add_command(CreateFile.new('file1.txt', 'hello world'))
cmds.add_command(CopyFile.new('file1.txt', 'file2.txt'))
cmds.add_command(DeleteFile.new('file1.txt'))

puts cmds.description
p '---'
cmds.execute
p '---'
cmds.unexecute

注意

Commandパターンのポイントは何を行うかの決定と、それの実行とを分離することです。

まとめ

Commandパターンでは、ある特定の動作を実行するオブジェクトを構築します。「特定」という部分が重要です。

第7章 コレクションを操作する: Iterator

2019-06-25

「Rubyによるデザインパターン」の読書メモです。

時々、個人の見解入りです。

集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する

外部イテレータ

class ArrayIterator
  def initialize(array)
    @array = array
    @index = 0
  end

  def has_next?
    @index < @array.length
  end

  def item
    @array[@index]
  end

  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end

array = ['red','green', 'blue']
i = ArrayIterator.new(array)
while i.has_next?
  p "item: #{i.next_item}"
end

内部イテレータ

def for_each_element(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
end

a = [10, 20, 30]
for_each_element(a) { |element| p "The element is #{element}" }

外部イテレータと内部イテレータ

外部イテレータはクライアントが繰り返しを制御します。

内部イテレータは始めから終わりまで実行されます。

Enumerable モジュールを使う

class Account
  attr_accessor :name, :balance

  def initialize(name, balance)
    @name = name
    @balance = balance
  end

  def <=>(other)
    balance <=> other.balance
  end
end

class Portfolio
  include Enumerable

  def initialize
    @accounts = []
  end

  def each(&block)
    @accounts.each(&block)
  end

  def add_account(account)
    @accounts << account
  end
end

a1 = Account.new('account1', 200)
a2 = Account.new('account2', 400)
a3 = Account.new('account3', 600)

portfolio = Portfolio.new
arr = [a1, a2, a3]
arr.each do |a|
  portfolio.add_account(a)
end

p portfolio.any? {|account| account.balance > 500}
p portfolio.all? {|account| account.balance > 500}

コードブロックで判定条件を渡せます。

注意点

test.rb

array = ['red', 'green', 'blue', 'purple']

array.each do |color|
  p color
  if color == 'green'
    array.delete(color)
  end
end

出力結果

$ ruby test.rb
"red"
"green"
"purple"

blue がスキップされてしまいました

test.rb

def change_resistant_for_each_element(array)
  copy = Array.new(array)
  i = 0
  while i < copy.length
    yield(copy[i])
    i += 1
  end
end

array = ['red', 'green', 'blue', 'purple']
change_resistant_for_each_element(array){|color|
  p color
  if color == 'green'
    array.delete(color)
  end
}

配列を削除しても、全部捜査されました。

出力結果

$ ruby test.rb
"red"
"green"
"blue"
"purple"

pandas で csv 出力する

2019-06-24

やったこと

pandas で csv ファイルの出力をします。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import pandas as pd
print(pd.__version__)
0.20.3

調査

to_csv を利用して csv ファイルを作成します。

引数の index は、出力したファイルに index を含むかどうかです。

data = {'state': ['Ohio', 'Nevada'],
       'year': [2000, 2001]}
df = DataFrame(data)
df.to_csv("hoge.csv", index=False)

出力された hoge.csv

state,year
Ohio,2000
Nevada,2001

参考