Python: Pseudo-random color from a string - python

I am doing some data visualization with Python in Blender and I need to assign colors to the data being represented. There is too much data to spend time hand-picking colors for each, so I want to generate the colors pseudo-randomly - I would like a given string to always result in the same color. That way, if it appears more than once, it will be clear that it is the same item.
For example, given a list like ['Moose', 'Frog', 'Your Mother'], let's say Moose would always be maroon.
The colors are specified in RGB, where each channel is a float from 0.0 to 1.0.
Here's what I've tried so far that isn't working:
import random
import hashlib
def norm(x, min, max): # this normalization function has a problem
normalized = ( x - min(x) ) / (max(x) - min(x) )
return normalized
def r(string, val):
h = hash(string)
print(h)
if h < 0:
h = h * -1
rs = random.seed( int(h) + int(val) )
output = norm(rs, 0.0, 1.0)
return output
my_list = ['Moose', 'Frog', 'Your Mother']
item = my_list[0]
color = [ r(item,1), r(item,2), (item,1) ]
print(color)
It results in TypeError: 'float' object is not callable but I don't know why. I'm trying to normalize the way this answer demonstrates.
It might be best to have a list of possible colors, as it allows for control over the palette. Either way, I need a pseudo-random float in the range of 0.0 ~ 0.1.

You can try with this function. It returns a rgb value for the name by using the 3 first letters.
def name_color(name):
color = [(ord(c.lower())-97)*8 for c in name[:3]]
return color

It turns out I didn't need to normalize or do anything fancy. random() returns a range between 0.0 ~ 1.0 as it is. I just needed to take a closer look at how random.seed() is supposed to be used.
Here's my solution:
import random
import hashlib
def r(string, int): # pseudo-randomization function
h = hash( string + str(int) ) # hash string and int together
if h < 0: # ensure positive number
h = h * -1
random.seed(h) # set the seed to use for randomization
output = random.random() # produce random value in range 0.0 ~ 1.0
output = round(output, 6) # round to 6 decimal places (optional)
return output
my_list = ['Toyota', 'Tesla', 'Mercedes-Benz']
item = my_list[0] # choose which list item
color = [ r(item,0), r(item,1), r(item,2) ] # R,G,B values
print(item, color)
Output: Toyota [0.049121, 0.383824, 0.635146]
I may try the list approach later.

Related

creating initial population for genetic algorithm

I need to create the initial population to solve this equation z = x⁵ - 10x³ + 30x - y² + 21y using genetic algorithm. The population must be binary and need to follow this rules:
X and Y range: [-2.5, 2.5]
The first bit represents the signal (0 or 1)
The second and third bit represents the integer part, values from 0 to 2 (00, 01, 10)
The rest should represents the float part, values from 0 to 5000.
def pop(pop_size):
pop = []
for i in range(pop_size):
for j in range(2):
signal = bin(np.random.randint(0, 2))[2:]
integer = bin(np.random.randint(0, 3))[2:]
float = bin(np.random.randint(0, 5001))[2:].zfill(13)
binary = [signal, integer, float]
binary = [''.join(binary)]
pop.append(binary)
return pop
My output right now looks like this: [['1110001000110000'], ['1100010010011000'], ['11000100010001010'], ['0100011000000010'], ['0100010111100001'], ['01000111001101110']]
But I need it to look like this: [['1110001000110000', '1100010010011000'], ['11000100010001010', '0100011000000010'], ['0100010111100001', '01000111001101110']] because each pair represents the value for X and Y.
Any idea of what I'm missing?
How about
def pop(pop_size)
rlt = []
for i in range(pop_size):
rlt.append([None,None])
for j in range(2):
signal = bin(np.random.randint(0, 2))[2:]
integer = bin(np.random.randint(0, 3))[2:]
floats = bin(np.random.randint(0, 5001))[2:].zfill(13)
rlt[-1][j] = signal+integer+floats
return rlt
Demo
>>> pop(3)
[['0100111010000110', '000110111010111'], ['100101100010010', '010010000101100'], ['0100011000011010', '0100111100001011']]

Python def (AttributeError: 'str' object has no attribute 'read')

Trying to convert the following to a def but doing something that's probably not allowed... What am I doing wrong and how could this be done better?
# Same for both
import alsaaudio
l_input = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, card='default')
r_input = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, card='default')
#
l, data = l_input.read()
if l > 0:
# transform data to logarithmic scale
lin_vu = (math.log(float(max(audioop.max(data, 2),1)))-log_lo)/(log_hi-log_lo)
# Calculate value
lin_vu = (min(max(int(lin_vu*15),0),15))
l, data = r_input.read()
if l > 0:
# transform data to logarithmic scale
rin_vu = (math.log(float(max(audioop.max(data, 2),1)))-log_lo)/(log_hi-log_lo)
# Calculate value
rin_vu = (min(max(int(rin_vu*15),0),15))
I was hoping to do something like this as I need to read 4 values, not just the two listed:
def readvu( src ):
l, data = src.read()
if l > 0:
# transform data to logarithmic scale
l_vu = (math.log(float(max(audioop.max(data, 2),1)))-log_lo)/(log_hi-log_lo)
# Calculate value
l_vu = (min(max(int(l_vu*15),0),15))
lin_vu = readvu( 'l_input' );
rin_vu = readvu( 'r_input' );
But that yields the mentioned error...
The solution is obvious: if you call readvu('l_input'), your src becomes 'l_input' and .read()ing from it will fail.
The call should be like
lin_vu = readvu(l_input)
rin_vu = readvu(r_input)
which passes the actual variables, not the strings.
Change of thought brought me to the following:
# Convert log scale, calculate value & max
def vu_log(data,vu_max,max_t):
# transform data to logarithmic scale
vu = (math.log(float(max(audioop.max(data, 2),1)))-log_lo)/(log_hi-log_lo)
# Calculate value
vu = (min(max(int(vu*15),0),15))
if vu >= vu_max:
max_t = (3 * vu_fps) # keep max for 3 seconds
vu_max = vu
else:
max_t = max(0, max_t-1) # Reduce max timer by 1 until 0
return (vu,vu_max,max_t);
# Fetch Left input VU from ALSA
l, data = l_input.read()
if l > 0:
# Transform data to logarithmic scale and calculate value
lin = vu_log(data,lin_max,lin_t)
lin_vu = lin[0] # Desired value
lin_max = lin[1] # Maximum value (last 3 sec)
lin_t = lin[2] # Used for tracking age of lin_max
Constructive suggestions always welcome... :)

Generating a Random Hex Color in Python

For a Django App, each "member" is assigned a color to help identify them. Their color is stored in the database and then printed/copied into the HTML when it is needed. The only issue is that I am unsure how to generate random Hex colors in python/django. It's easy enough to generate RGB colors, but to store them I would either need to a) make three extra columns in my "Member" model or b) store them all in the same column and use commas to separate them, then, later, parse the colors for the HTML. Neither of these are very appealing, so, again, I'm wondering how to generate random Hex colors in python/django.
import random
r = lambda: random.randint(0,255)
print('#%02X%02X%02X' % (r(),r(),r()))
Here is a simple way:
import random
color = "%06x" % random.randint(0, 0xFFFFFF)
To generate a random 3 char color:
import random
color = "%03x" % random.randint(0, 0xFFF)
%x in C-based languages is a string formatter to format integers as hexadecimal strings while 0x is the prefix to write numbers in base-16.
Colors can be prefixed with "#" if needed (CSS style)
little late to the party,
import random
chars = '0123456789ABCDEF'
['#'+''.join(random.sample(chars,6)) for i in range(N)]
Store it as a HTML color value:
Updated: now accepts both integer (0-255) and float (0.0-1.0) arguments. These will be clamped to their allowed range.
def htmlcolor(r, g, b):
def _chkarg(a):
if isinstance(a, int): # clamp to range 0--255
if a < 0:
a = 0
elif a > 255:
a = 255
elif isinstance(a, float): # clamp to range 0.0--1.0 and convert to integer 0--255
if a < 0.0:
a = 0
elif a > 1.0:
a = 255
else:
a = int(round(a*255))
else:
raise ValueError('Arguments must be integers or floats.')
return a
r = _chkarg(r)
g = _chkarg(g)
b = _chkarg(b)
return '#{:02x}{:02x}{:02x}'.format(r,g,b)
Result:
In [14]: htmlcolor(250,0,0)
Out[14]: '#fa0000'
In [15]: htmlcolor(127,14,54)
Out[15]: '#7f0e36'
In [16]: htmlcolor(0.1, 1.0, 0.9)
Out[16]: '#19ffe5'
This has been done before. Rather than implementing this yourself, possibly introducing errors, you may want to use a ready library, for example Faker. Have a look at the color providers, in particular hex_digit.
In [1]: from faker import Factory
In [2]: fake = Factory.create()
In [3]: fake.hex_color()
Out[3]: u'#3cae6a'
In [4]: fake.hex_color()
Out[4]: u'#5a9e28'
Just store them as an integer with the three channels at different bit offsets (just like they are often stored in memory):
value = (red << 16) + (green << 8) + blue
(If each channel is 0-255). Store that integer in the database and do the reverse operation when you need to get back to the distinct channels.
import random
def hex_code_colors():
a = hex(random.randrange(0,256))
b = hex(random.randrange(0,256))
c = hex(random.randrange(0,256))
a = a[2:]
b = b[2:]
c = c[2:]
if len(a)<2:
a = "0" + a
if len(b)<2:
b = "0" + b
if len(c)<2:
c = "0" + c
z = a + b + c
return "#" + z.upper()
So many ways to do this, so here's a demo using "colorutils".
pip install colorutils
It is possible to generate random values in (RGB, HEX, WEB, YIQ, HSV).
# docs and downloads at
# https://pypi.python.org/pypi/colorutils/
from colorutils import random_web
from tkinter import Tk, Button
mgui = Tk()
mgui.geometry('150x28+400+200')
def rcolor():
rn = random_web()
print(rn) # for terminal watchers
cbutton.config(text=rn)
mgui.config(bg=rn)
cbutton = Button(text="Click", command=rcolor)
cbutton.pack()
mgui.mainloop()
I certainly hope that was helpful.
import secrets
# generate 4 sets of 2-digit hex chars for a color with transparency
rgba = f"#{secrets.token_hex(4)}" # example return: "#ffff0000"
# generate 3 sets of 2-digit hex chars for a non-alpha color
rgb = f"#{secrets.token_hex(3)}" # example return: "#ab12ce"
import random
def generate_color():
color = '#{:02x}{:02x}{:02x}'.format(*map(lambda x: random.randint(0, 255), range(3)))
return color
Basically, this will give you a hashtag, a randint that gets converted to hex, and a padding of zeroes.
from random import randint
color = '#{:06x}'.format(randint(0, 256**3))
#Use the colors wherever you need!
For generating random anything, take a look at the random module
I would suggest you use the module to generate a random integer, take it's modulo 2**24, and treat the top 8 bits as R, that middle 8 bits as G and the bottom 8 as B.
It can all be accomplished with div/mod or bitwise operations.
hex_digits = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
digit_array = []
for i in xrange(6):
digit_array.append(hex_digits[randint(0,15)])
joined_digits = ''.join(digit_array)
color = '#' + joined_digits
import random
def get_random_hex:
random_number = random.randint(0,16777215)
# convert to hexadecimal
hex_number = str(hex(random_number))
# remove 0x and prepend '#'
return'#'+ hex_number[2:]
Would like to improve upon this solution as I found that it could generate color codes that have less than 6 characters. I also wanted to generate a function that would create a list that can be used else where such as for clustering in matplotlib.
import random
def get_random_hex:
random_number = random.randint(0,16777215)
# convert to hexadecimal
hex_number = str(hex(random_number))
# remove 0x and prepend '#'
return'#'+ hex_number[2:]
My proposal is :
import numpy as np
def color_generator (no_colors):
colors = []
while len(colors) < no_colors:
random_number = np.random.randint(0,16777215)
hex_number = format(random_number, 'x')
if len(hex_number) == 6:
hex_number = '#'+ hex_number
colors.append (hex_number)
return colors
Here's a simple code that I wrote based on what hexadecimal color notations represent:
import random
def getRandomCol():
r = hex(random.randrange(0, 255))[2:]
g = hex(random.randrange(0, 255))[2:]
b = hex(random.randrange(0, 255))[2:]
random_col = '#'+r+g+b
return random_col
The '#' in the hexadecimal color code just represents that the number represented is just a hexadecimal number. What's important is the next 6 digits. Pairs of 2 digits in those 6 hexadecimal digits represent the intensity of RGB (Red, Green, and Blue) each. The intensity of each color ranges between 0-255 and a combination of different intensities of RGB produces different colors.
For example, in #ff00ff, the first ff is equivalent to 255 in decimal, the next 00 is equivalent to 0 in decimal, and the last ff is equivalent to 255 in decimal. Therefore, #ff00ff in hexadecimal color coding is equivalent to RGB(255, 0, 255).
With this concept, here's the explanation of my approach:
Generated intensities of random numbers for each of r, g
and b
Converted those intensities into hexadecimal
Ignored the first 2 characters of each hexadecimal value '0x'
Concatenated '#' with the hexadecimal values r, g and b
intensities.
Feel free to refer to this link if you wanna know more about how colors work: https://hackernoon.com/hex-colors-how-do-they-work-d8cb935ac0f
Cheers!
Hi, maybe i could help with the next function that generate random Hex colors :
from colour import Color
import random as random
def Hex_color():
L = '0123456789ABCDEF'
return Color('#'+ ''.join([random.choice(L) for i in range(6)][:]))
from random import randbytes
randbytes(3).hex()
output
f5f2c9
There are a lot of complex answers here, this is what I used for a code of mine using one import line and one line to get a random code:
import random
color = '#' + ''.join(random.choices('0123456789ABCDEF', k=6))
print(color)
The output will be something like:
#3F67CD
If you want to make a list of color values, let's say, of 10 random colors, you can do the following:
import random as r
hex_chars = '0123456789ABCDEF'
num_colors = 10
colors = ['#' + ''.join(r.choices(hex_chars, k=6)) for _ in range(num_colors)]
print(colors)
And the output will be a list containing ten different random colors:
['#3DBEA2', '#0B3B64', '#31D196', '#6A98C2', '#9C1712', '#73AFFE', '#9F5E0D', '#A2F07E', '#EB6407', '#7E8FB6']
I hope that helps!

How to select an item from a list with known percentages in Python

I wish to select a random word from a list where the is a known chance for each word, for example:
Fruit with Probability
Orange 0.10
Apple 0.05
Mango 0.15
etc
How would be the best way of implementing this? The actual list I will take from is up to 100 items longs and the % do not all tally to 100 % they do fall short to account for the items that had a really low chance of occurrence. I would ideally like to take this from a CSV which is where I store this data. This is not a time critical task.
Thank you for any advice on how best to proceed.
You can pick items with weighted probabilities if you assign each item a number range proportional to its probability, pick a random number between zero and the sum of the ranges and find what item matches it. The following class does exactly that:
from random import random
class WeightedChoice(object):
def __init__(self, weights):
"""Pick items with weighted probabilities.
weights
a sequence of tuples of item and it's weight.
"""
self._total_weight = 0.
self._item_levels = []
for item, weight in weights:
self._total_weight += weight
self._item_levels.append((self._total_weight, item))
def pick(self):
pick = self._total_weight * random()
for level, item in self._item_levels:
if level >= pick:
return item
You can then load the CSV file with the csv module and feed it to the WeightedChoice class:
import csv
weighed_items = [(item,float(weight)) for item,weight in csv.reader(open('file.csv'))]
picker = WeightedChoice(weighed_items)
print(picker.pick())
What you want is to draw from a multinomial distribution. Assuming you have two lists of items and probabilities, and the probabilities sum to 1 (if not, just add some default value to cover the extra):
def choose(items,chances):
import random
p = chances[0]
x = random.random()
i = 0
while x > p :
i = i + 1
p = p + chances[i]
return items[i]
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]
x = 0.0
lst2 = []
for fruit, chance in lst:
tup = (x, fruit)
lst2.append(tup)
x += chance
tup = (x, None)
lst2.append(tup)
import random
def pick_one(lst2):
if lst2[0][1] is None:
raise ValueError, "no valid values to choose"
while True:
r = random.random()
for x, fruit in reversed(lst2):
if x <= r:
if fruit is None:
break # try again with a different random value
else:
return fruit
pick_one(lst2)
This builds a new list, with ascending values representing the range of values that choose a fruit; then pick_one() walks backward down the list, looking for a value that is <= the current random value. We put a "sentinel" value on the end of the list; if the values don't reach 1.0, there is a chance of a random value that shouldn't match anything, and it will match the sentinel value and then be rejected. random.random() returns a random value in the range [0.0, 1.0) so it is certain to match something in the list eventually.
The nice thing here is that you should be able to have one value with a 0.000001 chance of matching, and it should actually match with that frequency; the other solutions, where you make a list with the items repeated and just use random.choice() to choose one, would require a list with a million items in it to handle this case.
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]
x = 0.0
lst2 = []
for fruit, chance in lst:
low = x
high = x + chance
tup = (low, high, fruit)
lst2.append(tup)
x += chance
if x > 1.0:
raise ValueError, "chances add up to more than 100%"
low = x
high = 1.0
tup = (low, high, None)
lst2.append(tup)
import random
def pick_one(lst2):
if lst2[0][2] is None:
raise ValueError, "no valid values to choose"
while True:
r = random.random()
for low, high, fruit in lst2:
if low <= r < high:
if fruit is None:
break # try again with a different random value
else:
return fruit
pick_one(lst2)
# test it 10,000 times
d = {}
for i in xrange(10000):
x = pick_one(lst2)
if x in d:
d[x] += 1
else:
d[x] = 1
I think this is a little clearer. Instead of a tricky way of representing ranges as ascending values, we just keep ranges. Because we are testing ranges, we can simply walk forward through the lst2 values; no need to use reversed().
from numpy.random import multinomial
import numpy as np
def pickone(dist):
return np.where(multinomial(1, dist) == 1)[0][0]
if __name__ == '__main__':
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.70) ]
dist = [p[1] for p in lst]
N = 10000
draws = np.array([pickone(dist) for i in range(N)], dtype=int)
hist = np.histogram(draws, bins=[i for i in range(len(dist)+1)])[0]
for i in range(len(lst)):
print(f'{lst[i]} {hist[i]/N}')
One solution is to normalize the probabilities to integers and then repeat each element once per value (e.g. a list with 2 Oranges, 1 Apple, 3 Mangos). This is incredibly easy to do (from random import choice). If that is not practical, try the code here.
import random
d= {'orange': 0.10, 'mango': 0.15, 'apple': 0.05}
weightedArray = []
for k in d:
weightedArray+=[k]*int(d[k]*100)
random.choice(weightedArray)
EDITS
This is essentially what Brian said above.

I need a Python Function that will output a random string of 4 different characters when given the desired probabilites of the characters

For example,
The function could be something like def RandABCD(n, .25, .34, .25, .25):
Where n is the length of the string to be generated and the following numbers are the desired probabilities of A, B, C, D.
I would imagine this is quite simple, however i am having trouble creating a working program. Any help would be greatly appreciated.
Here's the code to select a single weighted value. You should be able to take it from here. It uses bisect and random to accomplish the work.
from bisect import bisect
from random import random
def WeightedABCD(*weights):
chars = 'ABCD'
breakpoints = [sum(weights[:x+1]) for x in range(4)]
return chars[bisect(breakpoints, random())]
Call it like this: WeightedABCD(.25, .34, .25, .25).
EDIT: Here is a version that works even if the weights don't add up to 1.0:
from bisect import bisect_left
from random import uniform
def WeightedABCD(*weights):
chars = 'ABCD'
breakpoints = [sum(weights[:x+1]) for x in range(4)]
return chars[bisect_left(breakpoints, uniform(0.0,breakpoints[-1]))]
The random class is quite powerful in python. You can generate a list with the characters desired at the appropriate weights and then use random.choice to obtain a selection.
First, make sure you do an import random.
For example, let's say you wanted a truly random string from A,B,C, or D.
1. Generate a list with the characters
li = ['A','B','C','D']
Then obtain values from it using random.choice
output = "".join([random.choice(li) for i in range(0, n)])
You could easily make that a function with n as a parameter.
In the above case, you have an equal chance of getting A,B,C, or D.
You can use duplicate entries in the list to give characters higher probabilities. So, for example, let's say you wanted a 50% chance of A and 25% chances of B and C respectively. You could have an array like this:
li = ['A','A','B','C']
And so on.
It would not be hard to parameterize the characters coming in with desired weights, to model that I'd use a dictionary.
characterbasis = {'A':25, 'B':25, 'C':25, 'D':25}
Make that the first parameter, and the second being the length of the string and use the above code to generate your string.
For four letters, here's something quick off the top of my head:
from random import random
def randABCD(n, pA, pB, pC, pD):
# assumes pA + pB + pC + pD == 1
cA = pA
cB = cA + pB
cC = cB + pC
def choose():
r = random()
if r < cA:
return 'A'
elif r < cB:
return 'B'
elif r < cC:
return 'C'
else:
return 'D'
return ''.join([choose() for i in xrange(n)])
I have no doubt that this can be made much cleaner/shorter, I'm just in a bit of a hurry right now.
The reason I wouldn't be content with David in Dakota's answer of using a list of duplicate characters is that depending on your probabilities, it may not be possible to create a list with duplicates in the right numbers to simulate the probabilities you want. (Well, I guess it might always be possible, but you might wind up needing a huge list - what if your probabilities were 0.11235442079, 0.4072777384, 0.2297927874, 0.25057505341?)
EDIT: here's a much cleaner generic version that works with any number of letters with any weights:
from bisect import bisect
from random import uniform
def rand_string(n, content):
''' Creates a string of letters (or substrings) chosen independently
with specified probabilities. content is a dictionary mapping
a substring to its "weight" which is proportional to its probability,
and n is the desired number of elements in the string.
This does not assume the sum of the weights is 1.'''
l, cdf = zip(*[(l, w) for l, w in content.iteritems()])
cdf = list(cdf)
for i in xrange(1, len(cdf)):
cdf[i] += cdf[i - 1]
return ''.join([l[bisect(cdf, uniform(0, cdf[-1]))] for i in xrange(n)])
Here is a rough idea of what might suit you
import random as r
def distributed_choice(probs):
r= r.random()
cum = 0.0
for pair in probs:
if (r < cum + pair[1]):
return pair[0]
cum += pair[1]
The parameter probs takes a list of pairs of the form (object, probability). It is assumed that the sum of probabilities is 1 (otherwise, its trivial to normalize).
To use it just execute:
''.join([distributed_choice(probs)]*4)
Hmm, something like:
import random
class RandomDistribution:
def __init__(self, kv):
self.entries = kv.keys()
self.where = []
cnt = 0
for x in self.entries:
self.where.append(cnt)
cnt += kv[x]
self.where.append(cnt)
def find(self, key):
l, r = 0, len(self.where)-1
while l+1 < r:
m = (l+r)/2
if self.where[m] <= key:
l=m
else:
r=m
return self.entries[l]
def randomselect(self):
return self.find(random.random()*self.where[-1])
rd = RandomDistribution( {"foo": 5.5, "bar": 3.14, "baz": 2.8 } )
for x in range(1000):
print rd.randomselect()
should get you most of the way...
Thank you all for your help, I was able to figure something out, mostly with this info.
For my particular need, I did something like this:
import random
#Create a function to randomize a given string
def makerandom(seq):
return ''.join(random.sample(seq, len(seq)))
def randomDNA(n, probA=0.25, probC=0.25, probG=0.25, probT=0.25):
notrandom=''
A=int(n*probA)
C=int(n*probC)
T=int(n*probT)
G=int(n*probG)
#The remainder part here is used to make sure all n are used, as one cannot
#have half an A for example.
remainder=''
for i in range(0, n-(A+G+C+T)):
ramainder+=random.choice("ATGC")
notrandom=notrandom+ 'A'*A+ 'C'*C+ 'G'*G+ 'T'*T + remainder
return makerandom(notrandom)

Categories

Resources