I'm trying to do a very simple math problem but I don't know how to convert it into python. Basically I need to calculate the mean entry price for a trade based on multiple "buy" entries. To do that all one needs to do is calculate
∑ (entry.amount * entry.price) / ∑ (entry.amount)
This should be the variable "total_entry_price2" in the end.
Where am I going wrong with the calculation? How Can I add all the ∑'s together?
Is this the best way to do it?
models.py
class Trade(models.Model):
...
class Entry(models.Model):
...
trade = models.ForeignKey(Trade, on_delete=models.CASCADE)
amount = models.FloatField()
price = models.FloatField()
entry_type = models.CharField(max_length=3, choices=ENTRY_TYPE_CHOICES, default=BUY)
views.py
#login_required
def trade_detail_view(request, pk):
logger.info('trade detail view')
if request.method == 'GET':
trade = get_object_or_404(Trade, pk=pk)
entries = Entry.objects.filter(trade=trade)
entries_buy = Entry.objects.filter(trade=trade, entry_type="buy")
patterns = Pattern.objects.filter(trade=trade)
for entry in entries_buy:
total_entry_price = Sum(entry.amount * entry.price)
total_entry_price2 = total_entry_price / entries_buy.aggregate(Sum('amount'))
print(total_entry_price2)
context = {
'trade': trade,
'entries': entries,
'patterns': patterns,
'max_amount': entries_buy.aggregate(Sum('amount')),
'total_fees': entries.aggregate(Sum('fee')),
'entry_price': entries_buy.aggregate(Avg('price'))
}
Current Terminal print:
Sum(Value(60.0)) / Value({'amount__sum': 40.0})
Sum(Value(10.0)) / Value({'amount__sum': 40.0})
Example data
The correct answer should be $1.75
(30 * 2 + 10 * 1) / 40 = 1.75
Final Solution (added upon from Oleg Russkin's Answer)
The revisions I did are as follows:
total_entry_cost = entries_buy.annotate(
s=F('amount') * F('price')
).aggregate(
total_entry_cost=ExpressionWrapper(
Sum(
Cast('s', output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['total_entry_cost']
print(total_entry_cost)
Example query to calculate required value.
Cast() to float may be avoided if the result of Sum is float, not an integer.
from django.db import models
from django.db.models import ExpressionWrapper, F, Sum
from django.db.models.functions import Cast
total_entry_price2 = Entry.objects.annotate(
s=F('amount')+F('price')
).aggregate(
price2=ExpressionWrapper(
Sum(
Cast('s',output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['price2']
# Actual result of the query is dictioanry
# so we get the key
# {'price2': 0.59633706227207}
Updated Answer By OP
This answer almost got us all the way. It was my fault not to be more clear on the exact answer I was looking for. I updated my question near the end to reflect it.
The revisions I did are as follows:
total_entry_cost = entries_buy.annotate(
s=F('amount') * F('price')
).aggregate(
total_entry_cost=ExpressionWrapper(
Sum(
Cast('s', output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['total_entry_cost']
print(total_entry_cost)
Related
I've created a form for being stored ratings and feedback in the database. Ratings and Feedback are being stored in the database perfectly. But the problem is, I can't find out how many different types of rating stars are present in the database. How can I find out how many 1 star/2star/---5 stars are present in the object model in percentage? What should I do?
models.py:
class Frontend_Rating(models.Model):
USer = models.ForeignKey(User,default=None,on_delete=models.CASCADE, related_name="frontend_rating")
Rating = models.IntegerField(null=True)
Feedback = models.TextField(max_length=250, null=True)
def __str__(self):
return str(self.pk)+ str(".") + str(self.USer) + str("(") + str(self.Rating) + str("stars") +str(")")
Views.py:
def index(request):
#rating____________________
frontend_all_ratings = Frontend_Rating.objects.all()
number_of_frontend_rating = frontend_all_ratings.count()
average_rating = 0
frontend_one_star = 0
frontend_two_star = 0
frontend_three_star = 0
frontend_four_star = 0
frontend_five_star = 0
percentage_frontend_ONE_star = 0
percentage_frontend_FIVE_star = 0
for frontend_rating_item in frontend_all_ratings:
frontend_rating = frontend_rating_item.Rating
if frontend_rating:
total_ratings = 0
total_ratings += frontend_rating
average_rating = round(total_ratings/frontend_all_ratings.count(),1)
context = {
"frontend_five_star":frontend_five_star,
"frontend_one_star":frontend_one_star,
"total_ratings":total_ratings,
"average_rating":average_rating,
}
return render(request,'0_index.html',context)
You can obtain the data with a single query with:
from django.db.models import Avg, BooleanField, ExpressionWrapper, Q
data = Frontend_Rating.objects.aggregate(
frontend_one_star=Avg(ExpressionWrapper(Q(Rating=1), output_field=BooleanField())),
frontend_two_star=Avg(ExpressionWrapper(Q(Rating=2), output_field=BooleanField())),
frontend_three_star=Avg(ExpressionWrapper(Q(Rating=3), output_field=BooleanField())),
frontend_four_star=Avg(ExpressionWrapper(Q(Rating=4), output_field=BooleanField())),
frontend_five_star=Avg(ExpressionWrapper(Q(Rating=5), output_field=BooleanField())),
)
frontend_one_star = data['frontend_one_star']
frontend_two_star = data['frontend_two_star']
frontend_three_star = data['frontend_three_star']
frontend_four_star = data['frontend_four_star']
frontend_five_star = data['frontend_five_star']
or for databases that use integer division, like postgresql:
from django.db.models import Count, FloatField, Q
data = Frontend_Rating.objects.aggregate(
frontend_one_star=Count('pk', filter=Q(Rating=1), output_field=FloatField()) / Count('pk', output_field=FloatField()),
frontend_two_star=Count('pk', filter=Q(Rating=2), output_field=FloatField()) / Count('pk', output_field=FloatField()),
frontend_three_star=Count('pk', filter=Q(Rating=3), output_field=FloatField()) / Count('pk', output_field=FloatField()),
frontend_four_star=Count('pk', filter=Q(Rating=4), output_field=FloatField()) / Count('pk', output_field=FloatField()),
frontend_five_star=Count('pk', filter=Q(Rating=5), output_field=FloatField()) / Count('pk', output_field=FloatField()),
)
frontend_one_star = data['frontend_one_star']
frontend_two_star = data['frontend_two_star']
frontend_three_star = data['frontend_three_star']
frontend_four_star = data['frontend_four_star']
frontend_five_star = data['frontend_five_star']
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from Frontend_Rating to FrontendRating.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: rating instead of Rating.
number_of_frontend_rating = Frontend_Rating.objects.count()
# Divide value by overall count to get ratio and multiply ratio by 100 to get percentage
frontend_one_star = (Frontend_Rating.objects.filter(Rating=1).count() / overall_count)*100
frontend_two_star = (Frontend_Rating.objects.filter(Rating=2).count() / overall_count)*100
frontend_three_star = (Frontend_Rating.objects.filter(Rating=3).count() / overall_count)*100
frontend_four_star = (Frontend_Rating.objects.filter(Rating=4).count() / overall_count)*100
frontend_five_star = (Frontend_Rating.objects.filter(Rating=5).count() / overall_count)*100
Please correct me if I misunderstood your question
I have a model like this:
class Priority(models.Model):
base = models.FloatField(default=0)
job = models.JSONField()
users = models.JSONField()
and both job and users are similar.
like job = {'a':1,'b':2}, user = {'c':3,'d':4}
I want to get the sum ( base + job__a + users__c)
how can I write the filter statement,
and raw sql is fine too.
Thanks
You should accomplish this (updated):
queryset = Priority.objects.annotate(
a=Coalesce(
Cast(KeyTextTransform('a', 'job'), output_field=FloatField()),
Cast(V(0.0), output_field=FloatField())
),
c=Cast(KeyTextTransform('c', 'users'), output_field=FloatField()),
).annotate(
sum=Sum(
F('base') + F('a') + F('c'), output_field=FloatField()
)
)
for item in queryset:
print(item.sum)
So I have this two models:
class Freguesia(models.Model):
nome = models.CharField("Freguesia",max_length=255)
class Intervencao(models.Model):
freguesia = models.ForeignKey(Freguesia, on_delete=models.CASCADE, verbose_name="Freguesia")
.........
And I want to display in html the top 6 freguesias with more intervecao and the percentage they have in total
I already can display the freguesias and the number of intervencoes they have , but I don't know how to display the percentage.
My View:
def DashboardView(request):
freguesias = Freguesia.objects.annotate(num_intervencao=Count('intervencao')).order_by('-num_intervencao')[:6]
context = {
'freguesias':freguesias
}
You can do it using Cast and F
from django.db.models Count, F
from django.db.models.functions import Cast
freguesias = Freguesia.objects.annotate(
num_intervencao=Count('intervencao'),
total=Count('id')
).annotate(
percentage=Cast(F('num_intervencao') * 100.0 / F('total'), FloatField())
).order_by('-num_intervencao')[:6]
Important to calculate percentage in separate annotate not in one where dependant fields are calculated.
I have a model that has a type and value, based on the type I want to change the sign of the number to negative (Without changing it in the database)
class Foo(models.Model):
EARNING_CATEGORY = 'E'
DEDUCTION_CATEGORY = 'D'
CATEGORY_CHOICES = (
(EARNING_CATEGORY, 'Earning'),
(DEDUCTION_CATEGORY, 'Deduction'),
)
name = models.CharField(max_length=50)
category = models.CharField(max_length=1, choices=CATEGORY_CHOICES)
value = models.FloatField()
Now in order to aggregate over Foo I need to consider that values with category = D should be negative so I get the real sum.
What I tried so far is to change the sign during the save() method, but I don't want to show it to the user as negative in the application. So the only way I came up with is to calculate in a method using a for loop.
class Account(models.Model):
# feilds
def sum(self):
items = self.foo_set.all()
sum = 0
for i in items:
if i.category == Foo.DEDUCTION_CATEGORY:
sum -= i.value
else:
sum += i.value
return sum
You can annotate the current sign during your query like this:
from django.db.models import Case, Value as V, F, FloatField, When
items = self.foo_set.annotate(fixed_value=Case(
When(category=Foo.DEDUCTION_CATEGORY, then=V('-1')*F('value')),
default=F('value'),
output_field=FloatField())
).all()
So during annotation we do a condition check and if our category is equal to 'D', we change sign of value field, and then do your aggregation on fixed_value field.
And here's more info:
https://docs.djangoproject.com/en/1.9/ref/models/conditional-expressions/#case
I dont know if this is the best way to resolve my problem, if isn't , tell me plz :)
I have this model :
class userTrophy(models.Model):
user = models.ForeignKey(userInfo)
platinum = models.IntegerField()
gold = models.IntegerField()
silver = models.IntegerField()
bronze = models.IntegerField()
level = models.IntegerField()
perc_level = models.IntegerField()
date_update = models.DateField(default=datetime.now, blank=True)
Now i want to retrieve one user info, but i want add 3 new "columns" online :
total = platinum + gold + silver + bronze
point = platinum * 100 + gold * 50 + silver * 25 + bronze * 10
and sort by "point", after sort, put a new column, with a sequencial number: rank (1-n).
Can i do this ( or part of this ) working only with the model ?
I am sure there are many ways to achieve this behavior. The one I am thinking of right now is a Custom Model Manager and transient model fields.
Your class could look like so:
from django.db import models
from datetime import datetime
class UserTrophyManager(models.Manager):
def get_query_set(self):
query_set = super(UserTrophyManager, self).get_query_set()
for ut in query_set:
ut.total = ut.platinum + ut.gold + ut.silver + ut.bronze
ut.points = ut.platinum * 100 + ut.gold * 50 + ut.silver * 25 + ut.bronze * 10
return query_set
class UserTrophy(models.Model):
user = models.CharField(max_length=30)
platinum = models.IntegerField()
gold = models.IntegerField()
silver = models.IntegerField()
bronze = models.IntegerField()
level = models.IntegerField()
perc_level = models.IntegerField()
date_update = models.DateField(default=datetime.now, blank=True)
total = 0
point = 0
objects = UserTrophyManager()
class Meta:
ordering = ['points']
So you can use the following and get total and point calculated:
user_trophies = userTrophy.objects.all()
for user_trophy in user_trophies:
print user_trophy.total
Here's the way I would do it. Add the columns 'total' and 'points' to your model, like this:
class UserTrophy(models.Model):
...
total = models.IntegerField()
points = models.IntegerField()
...
Override the save method for your model:
def save(self, *args, **kwargs):
# Compute the total and points before saving
self.total = self.platinum + self.gold + self.silver + self.bronze
self.points = self.platinum * 100 + self.gold * 50 + \
self.silver * 25 + self.bronze * 10
# Now save the object by calling the super class
super(UserTrophy, self).save(*args, **kwargs)
With total and points as first class citizens on your model, your concept of "rank" becomes just a matter of ordering and slicing the UserTrophy objects.
top_ten = UserTrophy.objects.order_by('-points')[:10]
You'll also want to make sure you have your fields indexed, so your queries are efficient.
If you don't like the idea of putting these fields in your model, you might be able to use the extra feature of Django query set objects to compute your total and points on the fly. I don't use this very often, so maybe someone else can put together an example.
Also, I recommend for you to read PEP 8 for Python coding conventions.
This is more of a followup question than an answer, but is it possible to do something like:
class userTrophy(models.Model):
... stuff...
def points(self):
self.gold + self.silver + self.bronze
then call something like object.points in a template. Im just curious if that is a possibility